Subversion Repositories Games.Carmageddon

Rev

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

  1. #include "harness.h"
  2. #include "ascii_tables.h"
  3. #include "brender_emu/renderer_impl.h"
  4. #include "include/harness/config.h"
  5. #include "include/harness/hooks.h"
  6. #include "include/harness/os.h"
  7. #include "platforms/null.h"
  8. #include "sound/sound.h"
  9. //#include "version.h" // Pierre-Marie Baty -- our port doesn't need that (though it's actually based on dethrace-0.7.1)
  10.  
  11. #include <errno.h>
  12. #include <stdio.h>
  13. #include <string.h>
  14. #include <sys/stat.h>
  15.  
  16. br_pixelmap* palette;
  17. uint32_t* screen_buffer;
  18. harness_br_renderer* renderer_state;
  19.  
  20. br_pixelmap* last_dst = NULL;
  21. br_pixelmap* last_src = NULL;
  22.  
  23. unsigned int last_frame_time = 0;
  24. int force_null_platform = 0;
  25.  
  26. extern unsigned int GetTotalTime(void);
  27.  
  28. extern br_v1db_state v1db;
  29. extern uint32_t gI_am_cheating;
  30.  
  31. // SplatPack or Carmageddon. This is where we represent the code differences between the two. For example, the intro smack file.
  32. tHarness_game_info harness_game_info;
  33.  
  34. // Configuration options
  35. tHarness_game_config harness_game_config;
  36.  
  37. // Platform hooks
  38. tHarness_platform gHarness_platform;
  39.  
  40. extern void Harness_Platform_Init(tHarness_platform* platform);
  41.  
  42. int Harness_ProcessCommandLine(int* argc, char* argv[]);
  43.  
  44. static void Harness_DetectGameMode(void) {
  45.     if (access("DATA/RACES/CASTLE.TXT", F_OK) != -1) {
  46.         // All splatpack edition have the castle track
  47.         if (access("DATA/RACES/CASTLE2.TXT", F_OK) != -1) {
  48.             // Only the full splat release has the castle2 track
  49.             harness_game_info.defines.INTRO_SMK_FILE = "SPLINTRO.SMK";
  50.             harness_game_info.defines.GERMAN_LOADSCRN = "LOADSCRN.PIX";
  51.             harness_game_info.mode = eGame_splatpack;
  52.             printf("Game mode: Splat Pack\n");
  53.         } else if (access("DATA/RACES/TINSEL.TXT", F_OK) != -1) {
  54.             // Only the the splat x-mas demo has the tinsel track
  55.             harness_game_info.defines.INTRO_SMK_FILE = "MIX_INTR.SMK";
  56.             harness_game_info.defines.GERMAN_LOADSCRN = "";
  57.             harness_game_info.mode = eGame_splatpack_xmas_demo;
  58.             printf("Game mode: Splat Pack X-mas demo\n");
  59.         } else {
  60.             // Assume we're using the splatpack demo
  61.             harness_game_info.defines.INTRO_SMK_FILE = "MIX_INTR.SMK";
  62.             harness_game_info.defines.GERMAN_LOADSCRN = "";
  63.             harness_game_info.mode = eGame_splatpack_demo;
  64.             printf("Game mode: Splat Pack demo\n");
  65.         }
  66.     } else if (access("DATA/RACES/CITYB3.TXT", F_OK) != -1) {
  67.         // All non-splatpack edition have the cityb3 track
  68.         if (access("DATA/RACES/CITYA1.TXT", F_OK) == -1) {
  69.             // The demo does not have the citya1 track
  70.             harness_game_info.defines.INTRO_SMK_FILE = "";
  71.             harness_game_info.defines.GERMAN_LOADSCRN = "COWLESS.PIX";
  72.             harness_game_info.mode = eGame_carmageddon_demo;
  73.             printf("Game mode: Carmageddon demo\n");
  74.         } else {
  75.             goto carmageddon;
  76.         }
  77.     } else {
  78.     carmageddon:
  79.         if (access("DATA/CUTSCENE/Mix_intr.smk", F_OK) == -1) {
  80.             harness_game_info.defines.INTRO_SMK_FILE = "Mix_intr.smk";
  81.         } else {
  82.             harness_game_info.defines.INTRO_SMK_FILE = "MIX_INTR.SMK";
  83.         }
  84.         harness_game_info.defines.GERMAN_LOADSCRN = "LOADSCRN.PIX";
  85.         harness_game_info.mode = eGame_carmageddon;
  86.         printf("Game mode: Carmageddon\n");
  87.     }
  88.  
  89.     harness_game_info.localization = eGameLocalization_none;
  90.     if (access("DATA/TRNSLATE.TXT", F_OK) != -1) {
  91.         FILE* f = fopen("DATA/TRNSLATE.TXT", "rb");
  92.         fseek(f, 0, SEEK_END);
  93.         int filesize = ftell(f);
  94.         fseek(f, 0, SEEK_SET);
  95.         char* buffer = malloc(filesize + 1);
  96.         int nb = fread(buffer, 1, filesize, f);
  97.         if (nb != filesize) {
  98.             LOG_PANIC("Unable to read DATA/TRNSLATE.TXT");
  99.         }
  100.         buffer[filesize] = '\0';
  101.         fclose(f);
  102.         if (strstr(buffer, "NEUES SPIEL") != NULL) {
  103.             harness_game_info.localization = eGameLocalization_german;
  104.             LOG_INFO("Language: \"%s\"", "German");
  105.         } else {
  106.             LOG_INFO("Language: unrecognized");
  107.         }
  108.         free(buffer);
  109.     }
  110.  
  111.     switch (harness_game_info.mode) {
  112.     case eGame_carmageddon:
  113.         switch (harness_game_info.localization) {
  114.         case eGameLocalization_german:
  115.             harness_game_info.defines.requires_ascii_table = 1;
  116.             harness_game_info.defines.ascii_table = carmageddon_german_ascii_tables.ascii;
  117.             harness_game_info.defines.ascii_shift_table = carmageddon_german_ascii_tables.ascii_shift;
  118.             break;
  119.         default:
  120.             harness_game_info.defines.ascii_table = carmageddon_ascii_tables.ascii;
  121.             harness_game_info.defines.ascii_shift_table = carmageddon_ascii_tables.ascii_shift;
  122.             break;
  123.         }
  124.         break;
  125.     case eGame_carmageddon_demo:
  126.         harness_game_info.defines.ascii_table = demo_ascii_tables.ascii;
  127.         harness_game_info.defines.ascii_shift_table = demo_ascii_tables.ascii_shift;
  128.         break;
  129.     case eGame_splatpack_demo:
  130.     case eGame_splatpack_xmas_demo:
  131.         harness_game_info.defines.ascii_table = xmas_ascii_tables.ascii;
  132.         harness_game_info.defines.ascii_shift_table = xmas_ascii_tables.ascii_shift;
  133.         break;
  134.     default:
  135.         break;
  136.     }
  137. }
  138.  
  139. #ifdef __APPLE__
  140. #include <pwd.h> // for struct passwd and getpwuid()
  141. #endif /* __APPLE__ */
  142. void Harness_Init(int* argc, char* argv[]) {
  143.     int result;
  144. #ifndef DETHRACE_VERSION
  145. #define DETHRACE_VERSION "0.7.1-pmbaty" // Pierre-Marie Baty -- CMake fix
  146. #endif // !DETHRACE_VERSION
  147.     printf("Dethrace version: %s\n", DETHRACE_VERSION);
  148.  
  149.     memset(&harness_game_info, 0, sizeof(harness_game_info));
  150.  
  151.     // disable the original CD check code
  152.     harness_game_config.enable_cd_check = 0;
  153.     // original physics time step. Lower values seem to work better at 30+ fps
  154.     harness_game_config.physics_step_time = 40;
  155.     // do not limit fps by default
  156.     harness_game_config.fps = 0;
  157.     // do not freeze timer
  158.     harness_game_config.freeze_timer = 0;
  159.     // default demo time out is 240s
  160.     harness_game_config.demo_timeout = 240000;
  161.     // disable developer diagnostics by default
  162.     harness_game_config.enable_diagnostics = 0;
  163.     // no volume multiplier
  164.     harness_game_config.volume_multiplier = 1.0f;
  165.     // start window in windowed mode
  166.     harness_game_config.start_full_screen = 0;
  167.     // Emulate gore check
  168.     harness_game_config.gore_check = 0;
  169.     // Enable Sound Options menu
  170.     harness_game_config.sound_options = 1; // Pierre-Marie Baty -- invert option default value
  171.     // Skip binding socket to allow local network testing
  172.     harness_game_config.no_bind = 0;
  173.  
  174.     // install signal handler by default
  175.     harness_game_config.install_signalhandler = 1;
  176.  
  177.     Harness_ProcessCommandLine(argc, argv);
  178.  
  179.     if (harness_game_config.install_signalhandler) {
  180.         OS_InstallSignalHandler(argv[0]);
  181.     }
  182.  
  183.     char* root_dir = getenv("DETHRACE_ROOT_DIR");
  184.     if (root_dir != NULL) {
  185.         LOG_INFO("DETHRACE_ROOT_DIR is set to '%s'", root_dir);
  186.     } else {
  187.         root_dir = OS_Dirname(argv[0]);
  188. #ifdef __APPLE__
  189.         // Pierre-Marie Baty -- macOS .app fix
  190.         // peek in ~/Library/Application Support/<DOTTED BUNDLE ID>
  191.         // if the directory doesn't exist, create it and copy all the contents of root_dir in it
  192.         // this is necessary to have a read/write copy of the game data to not invalidate the app bundle's code signature
  193.         // disk space
  194.         {
  195.            extern int CFStringGetCString (void *theString, char *buffer, int bufferSize, uint32_t encoding); // avoid importing the whole Cocoa stuff
  196.            extern void *CFBundleGetIdentifier (void *bundle); // avoid importing the whole Cocoa stuff
  197.            extern void *CFBundleGetMainBundle (void); // avoid importing the whole Cocoa stuff
  198.            static char data_path[1024] = "";
  199.            char bundle_id[256];
  200.            char *homedir = getenv ("HOME");
  201.            if (homedir == NULL)
  202.               homedir = getpwuid (getuid ())->pw_dir;
  203.            CFStringGetCString (CFBundleGetIdentifier (CFBundleGetMainBundle ()), bundle_id, sizeof (bundle_id), 0x08000100 /*kCFStringEncodingUTF8*/);
  204.            sprintf (data_path, "%s/Library/Application Support/%s", homedir, bundle_id);
  205.            if (access (data_path, 0) != 0)
  206.            {
  207.               mkdir (data_path, 0755);
  208.               {
  209.                  char cp_cmd[2048];
  210.                  sprintf (cp_cmd, "cp -LR \"%s/../Resources/DATA\" \"%s\"", root_dir, data_path);
  211.                  system (cp_cmd);
  212.               }
  213.            }
  214.            root_dir = data_path;
  215.         }
  216. #endif /* __APPLE__ */
  217.         if (root_dir[0] == 0)
  218.             strcpy(root_dir, "."); // Pierre-Marie Baty -- consistency check
  219.     }
  220.     printf("Using root directory: %s\n", root_dir);
  221.     result = chdir(root_dir);
  222.     if (result != 0) {
  223.         LOG_PANIC("Failed to chdir. Error is %s", strerror(errno));
  224.     }
  225.  
  226.     if (harness_game_info.mode == eGame_none) {
  227.         Harness_DetectGameMode();
  228.     }
  229.  
  230.     if (force_null_platform) {
  231.         Null_Platform_Init(&gHarness_platform);
  232.     } else {
  233.         Harness_Platform_Init(&gHarness_platform);
  234.     }
  235. }
  236.  
  237. // used by unit tests
  238. void Harness_ForceNullPlatform(void) {
  239.     force_null_platform = 1;
  240. }
  241.  
  242. int Harness_ProcessCommandLine(int* argc, char* argv[]) {
  243.     for (int i = 1; i < *argc; i++) {
  244.         int handled = 0;
  245.  
  246.         if (strcasecmp(argv[i], "--cdcheck") == 0) {
  247.             harness_game_config.enable_cd_check = 1;
  248.             handled = 1;
  249.         } else if (strstr(argv[i], "--debug=") != NULL) {
  250.             char* s = strstr(argv[i], "=");
  251.             harness_debug_level = atoi(s + 1);
  252.             LOG_INFO("debug level set to %d", harness_debug_level);
  253.             handled = 1;
  254.         } else if (strstr(argv[i], "--physics-step-time=") != NULL) {
  255.             char* s = strstr(argv[i], "=");
  256.             harness_game_config.physics_step_time = atof(s + 1);
  257.             LOG_INFO("Physics step time set to %f", harness_game_config.physics_step_time);
  258.             handled = 1;
  259.         } else if (strstr(argv[i], "--fps=") != NULL) {
  260.             char* s = strstr(argv[i], "=");
  261.             harness_game_config.fps = atoi(s + 1);
  262.             LOG_INFO("FPS limiter set to %f", harness_game_config.fps);
  263.             handled = 1;
  264.         } else if (strcasecmp(argv[i], "--freeze-timer") == 0) {
  265.             LOG_INFO("Timer frozen");
  266.             harness_game_config.freeze_timer = 1;
  267.             handled = 1;
  268.         } else if (strcasecmp(argv[i], "--no-signal-handler") == 0) {
  269.             LOG_INFO("Don't install the signal handler");
  270.             harness_game_config.install_signalhandler = 0;
  271.             handled = 1;
  272.         } else if (strstr(argv[i], "--demo-timeout=") != NULL) {
  273.             char* s = strstr(argv[i], "=");
  274.             harness_game_config.demo_timeout = atoi(s + 1) * 1000;
  275.             LOG_INFO("Demo timeout set to %d milliseconds", harness_game_config.demo_timeout);
  276.             handled = 1;
  277.         } else if (strcasecmp(argv[i], "--i-am-cheating") == 0) {
  278.             gI_am_cheating = 0xa11ee75d;
  279.             handled = 1;
  280.         } else if (strcasecmp(argv[i], "--enable-diagnostics") == 0) {
  281.             harness_game_config.enable_diagnostics = 1;
  282.             handled = 1;
  283.         } else if (strstr(argv[i], "--volume-multiplier=") != NULL) {
  284.             char* s = strstr(argv[i], "=");
  285.             harness_game_config.volume_multiplier = atof(s + 1);
  286.             LOG_INFO("Volume multiplier set to %f", harness_game_config.volume_multiplier);
  287.             handled = 1;
  288.         } else if (strcasecmp(argv[i], "--full-screen") == 0) {
  289.             harness_game_config.start_full_screen = 1;
  290.             handled = 1;
  291.         } else if (strcasecmp(argv[i], "--gore-check") == 0) {
  292.             harness_game_config.gore_check = 1;
  293.             handled = 1;
  294.         } else if (strcasecmp(argv[i], "--no-sound-options") == 0) { // Pierre-Marie Baty -- invert option default value
  295.             harness_game_config.sound_options = 0; // Pierre-Marie Baty -- invert option default value
  296.             handled = 1;
  297.         } else if (strcasecmp(argv[i], "--no-bind") == 0) {
  298.             harness_game_config.no_bind = 1;
  299.             handled = 1;
  300.         } else if (strcasecmp(argv[i], "--no-music") == 0) {
  301.             harness_game_config.no_music = 1;
  302.             handled = 1;
  303.         }
  304.  
  305.         if (handled) {
  306.             // shift args downwards
  307.             for (int j = i; j < *argc - 1; j++) {
  308.                 argv[j] = argv[j + 1];
  309.             }
  310.             (*argc)--;
  311.             i--;
  312.         }
  313.     }
  314.  
  315.     return 0;
  316. }
  317.  
  318. // Render 2d back buffer
  319. void Harness_RenderScreen(br_pixelmap* dst, br_pixelmap* src) {
  320.     gHarness_platform.Renderer_FullScreenQuad((uint8_t*)src->pixels);
  321.  
  322.     last_dst = dst;
  323.     last_src = src;
  324. }
  325.  
  326. void Harness_Hook_BrV1dbRendererBegin(br_v1db_state* v1db) {
  327.     renderer_state = NewHarnessBrRenderer();
  328.     v1db->renderer = (br_renderer*)renderer_state;
  329. }
  330.  
  331. static int Harness_CalculateFrameDelay(void) {
  332.     if (harness_game_config.fps == 0) {
  333.         return 0;
  334.     }
  335.  
  336.     unsigned int now = GetTotalTime();
  337.  
  338.     if (last_frame_time != 0) {
  339.         unsigned int frame_time = now - last_frame_time;
  340.         last_frame_time = now;
  341.         if (frame_time < 100) {
  342.             int sleep_time = (1000 / harness_game_config.fps) - frame_time;
  343.             if (sleep_time > 5) {
  344.                 return sleep_time;
  345.             }
  346.         }
  347.     }
  348.     return 0;
  349. }
  350.  
  351. void Harness_Hook_renderActor(br_actor* actor, br_model* model, br_material* material, br_token type) {
  352.     gHarness_platform.Renderer_Model(actor, model, material, type, renderer_state->state.matrix.model_to_view);
  353. }
  354.  
  355. // Called by game to swap buffers at end of frame rendering
  356. void Harness_Hook_BrPixelmapDoubleBuffer(br_pixelmap* dst, br_pixelmap* src) {
  357.  
  358.     // draw the current colour_buffer (2d screen) contents
  359.     Harness_RenderScreen(dst, src);
  360.  
  361.     int delay_ms = Harness_CalculateFrameDelay();
  362.     gHarness_platform.SwapWindow();
  363.     if (delay_ms > 0) {
  364.         gHarness_platform.Sleep(delay_ms);
  365.     }
  366.  
  367.     gHarness_platform.Renderer_ClearBuffers();
  368.     last_frame_time = GetTotalTime();
  369. }
  370.  
  371. void Harness_RenderLastScreen(void) {
  372.     if (last_dst) {
  373.         Harness_RenderScreen(last_dst, last_src);
  374.         gHarness_platform.SwapWindow();
  375.     }
  376. }
  377.  
  378. // Filesystem hooks
  379. FILE* Harness_Hook_fopen(const char* pathname, const char* mode) {
  380.     return OS_fopen(pathname, mode);
  381. }
  382.