Rev 18 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | pmbaty | 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" |
||
| 14 | pmbaty | 9 | //#include "version.h" // Pierre-Marie Baty -- our port doesn't need that (though it's actually based on dethrace-0.7.1) |
| 1 | pmbaty | 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 | |||
| 11 | pmbaty | 139 | #ifdef __APPLE__ |
| 140 | #include <pwd.h> // for struct passwd and getpwuid() |
||
| 141 | #endif /* __APPLE__ */ |
||
| 1 | pmbaty | 142 | void Harness_Init(int* argc, char* argv[]) { |
| 143 | int result; |
||
| 144 | #ifndef DETHRACE_VERSION |
||
| 14 | pmbaty | 145 | #define DETHRACE_VERSION "0.7.1-pmbaty" // Pierre-Marie Baty -- CMake fix |
| 1 | pmbaty | 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; |
||
| 20 | pmbaty | 167 | // Emulate gore check |
| 11 | pmbaty | 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 |
||
| 1 | pmbaty | 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__ |
||
| 11 | pmbaty | 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 | } |
||
| 1 | pmbaty | 216 | #endif /* __APPLE__ */ |
| 217 | if (root_dir[0] == 0) |
||
| 218 | strcpy(root_dir, "."); // Pierre-Marie Baty -- consistency check |
||
| 219 | } |
||
| 20 | pmbaty | 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)); |
||
| 1 | pmbaty | 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], "="); |
||
| 20 | pmbaty | 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); |
||
| 1 | pmbaty | 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; |
||
| 11 | pmbaty | 291 | } else if (strcasecmp(argv[i], "--gore-check") == 0) { |
| 292 | harness_game_config.gore_check = 1; |
||
| 1 | pmbaty | 293 | handled = 1; |
| 11 | pmbaty | 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; |
||
| 1 | pmbaty | 297 | } else if (strcasecmp(argv[i], "--no-bind") == 0) { |
| 298 | harness_game_config.no_bind = 1; |
||
| 299 | handled = 1; |
||
| 300 | } |
||
| 301 | |||
| 302 | if (handled) { |
||
| 303 | // shift args downwards |
||
| 304 | for (int j = i; j < *argc - 1; j++) { |
||
| 305 | argv[j] = argv[j + 1]; |
||
| 306 | } |
||
| 307 | (*argc)--; |
||
| 308 | i--; |
||
| 309 | } |
||
| 310 | } |
||
| 311 | |||
| 312 | return 0; |
||
| 313 | } |
||
| 314 | |||
| 20 | pmbaty | 315 | // Render 2d back buffer |
| 316 | void Harness_RenderScreen(br_pixelmap* dst, br_pixelmap* src) { |
||
| 317 | gHarness_platform.Renderer_FullScreenQuad((uint8_t*)src->pixels); |
||
| 318 | |||
| 319 | last_dst = dst; |
||
| 320 | last_src = src; |
||
| 321 | } |
||
| 322 | |||
| 323 | void Harness_Hook_BrV1dbRendererBegin(br_v1db_state* v1db) { |
||
| 324 | renderer_state = NewHarnessBrRenderer(); |
||
| 325 | v1db->renderer = (br_renderer*)renderer_state; |
||
| 326 | } |
||
| 327 | |||
| 328 | static int Harness_CalculateFrameDelay(void) { |
||
| 329 | if (harness_game_config.fps == 0) { |
||
| 330 | return 0; |
||
| 331 | } |
||
| 332 | |||
| 333 | unsigned int now = GetTotalTime(); |
||
| 334 | |||
| 335 | if (last_frame_time != 0) { |
||
| 336 | unsigned int frame_time = now - last_frame_time; |
||
| 337 | last_frame_time = now; |
||
| 338 | if (frame_time < 100) { |
||
| 339 | int sleep_time = (1000 / harness_game_config.fps) - frame_time; |
||
| 340 | if (sleep_time > 5) { |
||
| 341 | return sleep_time; |
||
| 342 | } |
||
| 343 | } |
||
| 344 | } |
||
| 345 | return 0; |
||
| 346 | } |
||
| 347 | |||
| 348 | void Harness_Hook_renderActor(br_actor* actor, br_model* model, br_material* material, br_token type) { |
||
| 349 | gHarness_platform.Renderer_Model(actor, model, material, type, renderer_state->state.matrix.model_to_view); |
||
| 350 | } |
||
| 351 | |||
| 352 | // Called by game to swap buffers at end of frame rendering |
||
| 353 | void Harness_Hook_BrPixelmapDoubleBuffer(br_pixelmap* dst, br_pixelmap* src) { |
||
| 354 | |||
| 355 | // draw the current colour_buffer (2d screen) contents |
||
| 356 | Harness_RenderScreen(dst, src); |
||
| 357 | |||
| 358 | int delay_ms = Harness_CalculateFrameDelay(); |
||
| 359 | gHarness_platform.SwapWindow(); |
||
| 360 | if (delay_ms > 0) { |
||
| 361 | gHarness_platform.Sleep(delay_ms); |
||
| 362 | } |
||
| 363 | |||
| 364 | gHarness_platform.Renderer_ClearBuffers(); |
||
| 365 | last_frame_time = GetTotalTime(); |
||
| 366 | } |
||
| 367 | |||
| 368 | void Harness_RenderLastScreen(void) { |
||
| 369 | if (last_dst) { |
||
| 370 | Harness_RenderScreen(last_dst, last_src); |
||
| 371 | gHarness_platform.SwapWindow(); |
||
| 372 | } |
||
| 373 | } |
||
| 374 | |||
| 1 | pmbaty | 375 | // Filesystem hooks |
| 376 | FILE* Harness_Hook_fopen(const char* pathname, const char* mode) { |
||
| 377 | return OS_fopen(pathname, mode); |
||
| 378 | } |