Rev 11 | Go to most recent revision | Details | 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" |
||
| 9 | //#include "version.h" |
||
| 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 | void Harness_Init(int* argc, char* argv[]) { |
||
| 140 | int result; |
||
| 141 | #ifndef DETHRACE_VERSION |
||
| 142 | #define DETHRACE_VERSION "0.6.0-pmbaty" // Pierre-Marie Baty -- CMake fix |
||
| 143 | #endif // !DETHRACE_VERSION |
||
| 144 | printf("Dethrace version: %s\n", DETHRACE_VERSION); |
||
| 145 | |||
| 146 | memset(&harness_game_info, 0, sizeof(harness_game_info)); |
||
| 147 | |||
| 148 | // disable the original CD check code |
||
| 149 | harness_game_config.enable_cd_check = 0; |
||
| 150 | // original physics time step. Lower values seem to work better at 30+ fps |
||
| 151 | harness_game_config.physics_step_time = 40; |
||
| 152 | // do not limit fps by default |
||
| 153 | harness_game_config.fps = 0; |
||
| 154 | // do not freeze timer |
||
| 155 | harness_game_config.freeze_timer = 0; |
||
| 156 | // default demo time out is 240s |
||
| 157 | harness_game_config.demo_timeout = 240000; |
||
| 158 | // disable developer diagnostics by default |
||
| 159 | harness_game_config.enable_diagnostics = 0; |
||
| 160 | // no volume multiplier |
||
| 161 | harness_game_config.volume_multiplier = 1.0f; |
||
| 162 | // start window in windowed mode |
||
| 163 | harness_game_config.start_full_screen = 0; |
||
| 164 | // Emulate DOS behavior |
||
| 165 | harness_game_config.dos_mode = 0; |
||
| 166 | // Skip binding socket to allow local network testing |
||
| 167 | harness_game_config.no_bind = 0; |
||
| 168 | |||
| 169 | // install signal handler by default |
||
| 170 | harness_game_config.install_signalhandler = 1; |
||
| 171 | |||
| 172 | Harness_ProcessCommandLine(argc, argv); |
||
| 173 | |||
| 174 | if (harness_game_config.install_signalhandler) { |
||
| 175 | OS_InstallSignalHandler(argv[0]); |
||
| 176 | } |
||
| 177 | |||
| 178 | char* root_dir = getenv("DETHRACE_ROOT_DIR"); |
||
| 179 | if (root_dir != NULL) { |
||
| 180 | LOG_INFO("DETHRACE_ROOT_DIR is set to '%s'", root_dir); |
||
| 181 | } else { |
||
| 182 | root_dir = OS_Dirname(argv[0]); |
||
| 183 | #ifdef __APPLE__ |
||
| 184 | strcat (root_dir, "/../Resources"); // Pierre-Marie Baty -- macOS .app fix |
||
| 185 | #endif /* __APPLE__ */ |
||
| 186 | if (root_dir[0] == 0) |
||
| 187 | strcpy(root_dir, "."); // Pierre-Marie Baty -- consistency check |
||
| 188 | } |
||
| 189 | printf("Using root directory: %s\n", root_dir); |
||
| 190 | result = chdir(root_dir); |
||
| 191 | if (result != 0) { |
||
| 192 | LOG_PANIC("Failed to chdir. Error is %s", strerror(errno)); |
||
| 193 | } |
||
| 194 | |||
| 195 | if (harness_game_info.mode == eGame_none) { |
||
| 196 | Harness_DetectGameMode(); |
||
| 197 | } |
||
| 198 | |||
| 199 | if (force_null_platform) { |
||
| 200 | Null_Platform_Init(&gHarness_platform); |
||
| 201 | } else { |
||
| 202 | Harness_Platform_Init(&gHarness_platform); |
||
| 203 | } |
||
| 204 | } |
||
| 205 | |||
| 206 | // used by unit tests |
||
| 207 | void Harness_ForceNullPlatform(void) { |
||
| 208 | force_null_platform = 1; |
||
| 209 | } |
||
| 210 | |||
| 211 | int Harness_ProcessCommandLine(int* argc, char* argv[]) { |
||
| 212 | for (int i = 1; i < *argc; i++) { |
||
| 213 | int handled = 0; |
||
| 214 | |||
| 215 | if (strcasecmp(argv[i], "--cdcheck") == 0) { |
||
| 216 | harness_game_config.enable_cd_check = 1; |
||
| 217 | handled = 1; |
||
| 218 | } else if (strstr(argv[i], "--debug=") != NULL) { |
||
| 219 | char* s = strstr(argv[i], "="); |
||
| 220 | harness_debug_level = atoi(s + 1); |
||
| 221 | LOG_INFO("debug level set to %d", harness_debug_level); |
||
| 222 | handled = 1; |
||
| 223 | } else if (strstr(argv[i], "--physics-step-time=") != NULL) { |
||
| 224 | char* s = strstr(argv[i], "="); |
||
| 225 | harness_game_config.physics_step_time = atof(s + 1); |
||
| 226 | LOG_INFO("Physics step time set to %f", harness_game_config.physics_step_time); |
||
| 227 | handled = 1; |
||
| 228 | } else if (strstr(argv[i], "--fps=") != NULL) { |
||
| 229 | char* s = strstr(argv[i], "="); |
||
| 230 | harness_game_config.fps = atoi(s + 1); |
||
| 231 | LOG_INFO("FPS limiter set to %f", harness_game_config.fps); |
||
| 232 | handled = 1; |
||
| 233 | } else if (strcasecmp(argv[i], "--freeze-timer") == 0) { |
||
| 234 | LOG_INFO("Timer frozen"); |
||
| 235 | harness_game_config.freeze_timer = 1; |
||
| 236 | handled = 1; |
||
| 237 | } else if (strcasecmp(argv[i], "--no-signal-handler") == 0) { |
||
| 238 | LOG_INFO("Don't install the signal handler"); |
||
| 239 | harness_game_config.install_signalhandler = 0; |
||
| 240 | handled = 1; |
||
| 241 | } else if (strstr(argv[i], "--demo-timeout=") != NULL) { |
||
| 242 | char* s = strstr(argv[i], "="); |
||
| 243 | harness_game_config.demo_timeout = atoi(s + 1) * 1000; |
||
| 244 | LOG_INFO("Demo timeout set to %d milliseconds", harness_game_config.demo_timeout); |
||
| 245 | handled = 1; |
||
| 246 | } else if (strcasecmp(argv[i], "--i-am-cheating") == 0) { |
||
| 247 | gI_am_cheating = 0xa11ee75d; |
||
| 248 | handled = 1; |
||
| 249 | } else if (strcasecmp(argv[i], "--enable-diagnostics") == 0) { |
||
| 250 | harness_game_config.enable_diagnostics = 1; |
||
| 251 | handled = 1; |
||
| 252 | } else if (strstr(argv[i], "--volume-multiplier=") != NULL) { |
||
| 253 | char* s = strstr(argv[i], "="); |
||
| 254 | harness_game_config.volume_multiplier = atof(s + 1); |
||
| 255 | LOG_INFO("Volume multiplier set to %f", harness_game_config.volume_multiplier); |
||
| 256 | handled = 1; |
||
| 257 | } else if (strcasecmp(argv[i], "--full-screen") == 0) { |
||
| 258 | harness_game_config.start_full_screen = 1; |
||
| 259 | handled = 1; |
||
| 260 | } else if (strcasecmp(argv[i], "--dos-mode") == 0) { |
||
| 261 | harness_game_config.dos_mode = 1; |
||
| 262 | handled = 1; |
||
| 263 | } else if (strcasecmp(argv[i], "--no-bind") == 0) { |
||
| 264 | harness_game_config.no_bind = 1; |
||
| 265 | handled = 1; |
||
| 266 | } |
||
| 267 | |||
| 268 | if (handled) { |
||
| 269 | // shift args downwards |
||
| 270 | for (int j = i; j < *argc - 1; j++) { |
||
| 271 | argv[j] = argv[j + 1]; |
||
| 272 | } |
||
| 273 | (*argc)--; |
||
| 274 | i--; |
||
| 275 | } |
||
| 276 | } |
||
| 277 | |||
| 278 | return 0; |
||
| 279 | } |
||
| 280 | |||
| 281 | // Render 2d back buffer |
||
| 282 | void Harness_RenderScreen(br_pixelmap* dst, br_pixelmap* src) { |
||
| 283 | gHarness_platform.Renderer_FullScreenQuad((uint8_t*)src->pixels); |
||
| 284 | |||
| 285 | last_dst = dst; |
||
| 286 | last_src = src; |
||
| 287 | } |
||
| 288 | |||
| 289 | void Harness_Hook_BrV1dbRendererBegin(br_v1db_state* v1db) { |
||
| 290 | renderer_state = NewHarnessBrRenderer(); |
||
| 291 | v1db->renderer = (br_renderer*)renderer_state; |
||
| 292 | } |
||
| 293 | |||
| 294 | static int Harness_CalculateFrameDelay(void) { |
||
| 295 | if (harness_game_config.fps == 0) { |
||
| 296 | return 0; |
||
| 297 | } |
||
| 298 | |||
| 299 | unsigned int now = GetTotalTime(); |
||
| 300 | |||
| 301 | if (last_frame_time != 0) { |
||
| 302 | unsigned int frame_time = now - last_frame_time; |
||
| 303 | last_frame_time = now; |
||
| 304 | if (frame_time < 100) { |
||
| 305 | int sleep_time = (1000 / harness_game_config.fps) - frame_time; |
||
| 306 | if (sleep_time > 5) { |
||
| 307 | return sleep_time; |
||
| 308 | } |
||
| 309 | } |
||
| 310 | } |
||
| 311 | return 0; |
||
| 312 | } |
||
| 313 | |||
| 314 | void Harness_Hook_renderActor(br_actor* actor, br_model* model, br_material* material, br_token type) { |
||
| 315 | gHarness_platform.Renderer_Model(actor, model, material, type, renderer_state->state.matrix.model_to_view); |
||
| 316 | } |
||
| 317 | |||
| 318 | // Called by game to swap buffers at end of frame rendering |
||
| 319 | void Harness_Hook_BrPixelmapDoubleBuffer(br_pixelmap* dst, br_pixelmap* src) { |
||
| 320 | |||
| 321 | // draw the current colour_buffer (2d screen) contents |
||
| 322 | Harness_RenderScreen(dst, src); |
||
| 323 | |||
| 324 | int delay_ms = Harness_CalculateFrameDelay(); |
||
| 325 | gHarness_platform.SwapWindow(); |
||
| 326 | if (delay_ms > 0) { |
||
| 327 | gHarness_platform.Sleep(delay_ms); |
||
| 328 | } |
||
| 329 | |||
| 330 | gHarness_platform.Renderer_ClearBuffers(); |
||
| 331 | last_frame_time = GetTotalTime(); |
||
| 332 | } |
||
| 333 | |||
| 334 | void Harness_RenderLastScreen(void) { |
||
| 335 | if (last_dst) { |
||
| 336 | Harness_RenderScreen(last_dst, last_src); |
||
| 337 | gHarness_platform.SwapWindow(); |
||
| 338 | } |
||
| 339 | } |
||
| 340 | |||
| 341 | // Filesystem hooks |
||
| 342 | FILE* Harness_Hook_fopen(const char* pathname, const char* mode) { |
||
| 343 | return OS_fopen(pathname, mode); |
||
| 344 | } |