#include "harness.h"
 
#include "ascii_tables.h"
 
#include "brender_emu/renderer_impl.h"
 
#include "include/harness/config.h"
 
#include "include/harness/hooks.h"
 
#include "include/harness/os.h"
 
#include "platforms/null.h"
 
#include "sound/sound.h"
 
//#include "version.h" // Pierre-Marie Baty -- our port doesn't need that (though it's actually based on dethrace-0.7.1)
 
 
 
#include <errno.h>
 
#include <stdio.h>
 
#include <string.h>
 
#include <sys/stat.h>
 
 
 
br_pixelmap* palette;
 
uint32_t* screen_buffer;
 
harness_br_renderer* renderer_state;
 
 
 
br_pixelmap* last_dst = NULL;
 
br_pixelmap* last_src = NULL;
 
 
 
unsigned int last_frame_time = 0;
 
int force_null_platform = 0;
 
 
 
extern unsigned int GetTotalTime(void);
 
 
 
extern br_v1db_state v1db;
 
extern uint32_t gI_am_cheating;
 
 
 
// SplatPack or Carmageddon. This is where we represent the code differences between the two. For example, the intro smack file.
 
tHarness_game_info harness_game_info;
 
 
 
// Configuration options
 
tHarness_game_config harness_game_config;
 
 
 
// Platform hooks
 
tHarness_platform gHarness_platform;
 
 
 
extern void Harness_Platform_Init(tHarness_platform* platform);
 
 
 
int Harness_ProcessCommandLine(int* argc, char* argv[]);
 
 
 
static void Harness_DetectGameMode(void) {
 
    if (access("DATA/RACES/CASTLE.TXT", F_OK) != -1) {
 
        // All splatpack edition have the castle track
 
        if (access("DATA/RACES/CASTLE2.TXT", F_OK) != -1) {
 
            // Only the full splat release has the castle2 track
 
            harness_game_info.defines.INTRO_SMK_FILE = "SPLINTRO.SMK";
 
            harness_game_info.defines.GERMAN_LOADSCRN = "LOADSCRN.PIX";
 
            harness_game_info.mode = eGame_splatpack;
 
            printf("Game mode: Splat Pack\n");  
        } else if (access("DATA/RACES/TINSEL.TXT", F_OK) != -1) {
 
            // Only the the splat x-mas demo has the tinsel track
 
            harness_game_info.defines.INTRO_SMK_FILE = "MIX_INTR.SMK";
 
            harness_game_info.defines.GERMAN_LOADSCRN = "";
 
            harness_game_info.mode = eGame_splatpack_xmas_demo;
 
            printf("Game mode: Splat Pack X-mas demo\n");  
        } else {
 
            // Assume we're using the splatpack demo
 
            harness_game_info.defines.INTRO_SMK_FILE = "MIX_INTR.SMK";
 
            harness_game_info.defines.GERMAN_LOADSCRN = "";
 
            harness_game_info.mode = eGame_splatpack_demo;
 
            printf("Game mode: Splat Pack demo\n");  
        }
 
    } else if (access("DATA/RACES/CITYB3.TXT", F_OK) != -1) {
 
        // All non-splatpack edition have the cityb3 track
 
        if (access("DATA/RACES/CITYA1.TXT", F_OK) == -1) {
 
            // The demo does not have the citya1 track
 
            harness_game_info.defines.INTRO_SMK_FILE = "";
 
            harness_game_info.defines.GERMAN_LOADSCRN = "COWLESS.PIX";
 
            harness_game_info.mode = eGame_carmageddon_demo;
 
            printf("Game mode: Carmageddon demo\n");  
        } else {
 
            goto carmageddon;
 
        }
 
    } else {
 
    carmageddon:
 
        if (access("DATA/CUTSCENE/Mix_intr.smk", F_OK) == -1) {
 
            harness_game_info.defines.INTRO_SMK_FILE = "Mix_intr.smk";
 
        } else {
 
            harness_game_info.defines.INTRO_SMK_FILE = "MIX_INTR.SMK";
 
        }
 
        harness_game_info.defines.GERMAN_LOADSCRN = "LOADSCRN.PIX";
 
        harness_game_info.mode = eGame_carmageddon;
 
        printf("Game mode: Carmageddon\n");  
    }
 
 
 
    harness_game_info.localization = eGameLocalization_none;
 
    if (access("DATA/TRNSLATE.TXT", F_OK) != -1) {
 
        FILE
* f 
= fopen("DATA/TRNSLATE.TXT", "rb"); 
        char* buffer 
= malloc(filesize 
+ 1);  
        int nb 
= fread(buffer
, 1, filesize
, f
);  
        if (nb != filesize) {
 
            LOG_PANIC("Unable to read DATA/TRNSLATE.TXT");
 
        }
 
        buffer[filesize] = '\0';
 
        if (strstr(buffer
, "NEUES SPIEL") != NULL
) {  
            harness_game_info.localization = eGameLocalization_german;
 
            LOG_INFO("Language: \"%s\"", "German");
 
        } else {
 
            LOG_INFO("Language: unrecognized");
 
        }
 
    }
 
 
 
    switch (harness_game_info.mode) {
 
    case eGame_carmageddon:
 
        switch (harness_game_info.localization) {
 
        case eGameLocalization_german:
 
            harness_game_info.defines.requires_ascii_table = 1;
 
            harness_game_info.defines.ascii_table = carmageddon_german_ascii_tables.ascii;
 
            harness_game_info.defines.ascii_shift_table = carmageddon_german_ascii_tables.ascii_shift;
 
            break;
 
        default:
 
            harness_game_info.defines.ascii_table = carmageddon_ascii_tables.ascii;
 
            harness_game_info.defines.ascii_shift_table = carmageddon_ascii_tables.ascii_shift;
 
            break;
 
        }
 
        break;
 
    case eGame_carmageddon_demo:
 
        harness_game_info.defines.ascii_table = demo_ascii_tables.ascii;
 
        harness_game_info.defines.ascii_shift_table = demo_ascii_tables.ascii_shift;
 
        break;
 
    case eGame_splatpack_demo:
 
    case eGame_splatpack_xmas_demo:
 
        harness_game_info.defines.ascii_table = xmas_ascii_tables.ascii;
 
        harness_game_info.defines.ascii_shift_table = xmas_ascii_tables.ascii_shift;
 
        break;
 
    default:
 
        break;
 
    }
 
}
 
 
 
#ifdef __APPLE__
 
#include <pwd.h> // for struct passwd and getpwuid()
 
#endif /* __APPLE__ */
 
void Harness_Init(int* argc, char* argv[]) {
 
    int result;
 
#ifndef DETHRACE_VERSION
 
#define DETHRACE_VERSION "0.7.1-pmbaty" // Pierre-Marie Baty -- CMake fix
 
#endif // !DETHRACE_VERSION
 
    printf("Dethrace version: %s\n", DETHRACE_VERSION
);  
 
 
    memset(&harness_game_info
, 0, sizeof(harness_game_info
));  
 
 
    // disable the original CD check code
 
    harness_game_config.enable_cd_check = 0;
 
    // original physics time step. Lower values seem to work better at 30+ fps
 
    harness_game_config.physics_step_time = 40;
 
    // do not limit fps by default
 
    harness_game_config.fps = 0;
 
    // do not freeze timer
 
    harness_game_config.freeze_timer = 0;
 
    // default demo time out is 240s
 
    harness_game_config.demo_timeout = 240000;
 
    // disable developer diagnostics by default
 
    harness_game_config.enable_diagnostics = 0;
 
    // no volume multiplier
 
    harness_game_config.volume_multiplier = 1.0f;
 
    // start window in windowed mode
 
    harness_game_config.start_full_screen = 0;
 
    // Emulate gore check
 
    harness_game_config.gore_check = 0;
 
    // Enable Sound Options menu
 
    harness_game_config.sound_options = 1; // Pierre-Marie Baty -- invert option default value
 
    // Skip binding socket to allow local network testing
 
    harness_game_config.no_bind = 0;
 
 
 
    // install signal handler by default
 
    harness_game_config.install_signalhandler = 1;
 
 
 
    Harness_ProcessCommandLine(argc, argv);
 
 
 
    if (harness_game_config.install_signalhandler) {
 
        OS_InstallSignalHandler(argv[0]);
 
    }
 
 
 
    char* root_dir 
= getenv("DETHRACE_ROOT_DIR");  
    if (root_dir != NULL) {
 
        LOG_INFO("DETHRACE_ROOT_DIR is set to '%s'", root_dir);
 
    } else {
 
        root_dir = OS_Dirname(argv[0]);
 
#ifdef __APPLE__
 
        // Pierre-Marie Baty -- macOS .app fix
 
        // peek in ~/Library/Application Support/<DOTTED BUNDLE ID>
 
        // if the directory doesn't exist, create it and copy all the contents of root_dir in it
 
        // this is necessary to have a read/write copy of the game data to not invalidate the app bundle's code signature
 
        // disk space 
 
        {
 
           extern int CFStringGetCString (void *theString, char *buffer, int bufferSize, uint32_t encoding); // avoid importing the whole Cocoa stuff
 
           extern void *CFBundleGetIdentifier (void *bundle); // avoid importing the whole Cocoa stuff
 
           extern void *CFBundleGetMainBundle (void); // avoid importing the whole Cocoa stuff
 
           static char data_path[1024] = "";
 
           char bundle_id[256];
 
           char *homedir 
= getenv ("HOME");  
           if (homedir == NULL)
 
              homedir = getpwuid (getuid ())->pw_dir;
 
           CFStringGetCString (CFBundleGetIdentifier (CFBundleGetMainBundle ()), bundle_id, sizeof (bundle_id), 0x08000100 /*kCFStringEncodingUTF8*/);
 
           sprintf (data_path
, "%s/Library/Application Support/%s", homedir
, bundle_id
);  
           if (access (data_path, 0) != 0)
 
           {
 
              mkdir (data_path, 0755);
 
              {
 
                 char cp_cmd[2048];
 
                 sprintf (cp_cmd
, "cp -LR \"%s/../Resources/DATA\" \"%s\"", root_dir
, data_path
);  
              }
 
           }
 
           root_dir = data_path;
 
        }
 
#endif /* __APPLE__ */
 
        if (root_dir[0] == 0)
 
            strcpy(root_dir
, "."); // Pierre-Marie Baty -- consistency check  
    }
 
    printf("Using root directory: %s\n", root_dir
);  
    result = chdir(root_dir);
 
    if (result != 0) {
 
        LOG_PANIC
("Failed to chdir. Error is %s", strerror(errno
)); 
    }
 
 
 
    if (harness_game_info.mode == eGame_none) {
 
        Harness_DetectGameMode();
 
    }
 
 
 
    if (force_null_platform) {
 
        Null_Platform_Init(&gHarness_platform);
 
    } else {
 
        Harness_Platform_Init(&gHarness_platform);
 
    }
 
}
 
 
 
// used by unit tests
 
void Harness_ForceNullPlatform(void) {
 
    force_null_platform = 1;
 
}
 
 
 
int Harness_ProcessCommandLine(int* argc, char* argv[]) {
 
    for (int i = 1; i < *argc; i++) {
 
        int handled = 0;
 
 
 
        if (strcasecmp(argv[i], "--cdcheck") == 0) {
 
            harness_game_config.enable_cd_check = 1;
 
            handled = 1;
 
        } else if (strstr(argv
[i
], "--debug=") != NULL
) {  
            char* s 
= strstr(argv
[i
], "=");  
            harness_debug_level 
= atoi(s 
+ 1); 
            LOG_INFO("debug level set to %d", harness_debug_level);
 
            handled = 1;
 
        } else if (strstr(argv
[i
], "--physics-step-time=") != NULL
) {  
            char* s 
= strstr(argv
[i
], "=");  
            harness_game_config.
physics_step_time = atof(s 
+ 1); 
            LOG_INFO("Physics step time set to %f", harness_game_config.physics_step_time);
 
            handled = 1;
 
        } else if (strstr(argv
[i
], "--fps=") != NULL
) {  
            char* s 
= strstr(argv
[i
], "=");  
            harness_game_config.
fps = atoi(s 
+ 1); 
            LOG_INFO("FPS limiter set to %f", harness_game_config.fps);
 
            handled = 1;
 
        } else if (strcasecmp(argv[i], "--freeze-timer") == 0) {
 
            LOG_INFO("Timer frozen");
 
            harness_game_config.freeze_timer = 1;
 
            handled = 1;
 
        } else if (strcasecmp(argv[i], "--no-signal-handler") == 0) {
 
            LOG_INFO("Don't install the signal handler");
 
            harness_game_config.install_signalhandler = 0;
 
            handled = 1;
 
        } else if (strstr(argv
[i
], "--demo-timeout=") != NULL
) {  
            char* s 
= strstr(argv
[i
], "=");  
            harness_game_config.
demo_timeout = atoi(s 
+ 1) * 1000; 
            LOG_INFO("Demo timeout set to %d milliseconds", harness_game_config.demo_timeout);
 
            handled = 1;
 
        } else if (strcasecmp(argv[i], "--i-am-cheating") == 0) {
 
            gI_am_cheating = 0xa11ee75d;
 
            handled = 1;
 
        } else if (strcasecmp(argv[i], "--enable-diagnostics") == 0) {
 
            harness_game_config.enable_diagnostics = 1;
 
            handled = 1;
 
        } else if (strstr(argv
[i
], "--volume-multiplier=") != NULL
) {  
            char* s 
= strstr(argv
[i
], "=");  
            harness_game_config.
volume_multiplier = atof(s 
+ 1); 
            LOG_INFO("Volume multiplier set to %f", harness_game_config.volume_multiplier);
 
            handled = 1;
 
        } else if (strcasecmp(argv[i], "--full-screen") == 0) {
 
            harness_game_config.start_full_screen = 1;
 
            handled = 1;
 
        } else if (strcasecmp(argv[i], "--gore-check") == 0) {
 
            harness_game_config.gore_check = 1;
 
            handled = 1;
 
        } else if (strcasecmp(argv[i], "--no-sound-options") == 0) { // Pierre-Marie Baty -- invert option default value
 
            harness_game_config.sound_options = 0; // Pierre-Marie Baty -- invert option default value
 
            handled = 1;
 
        } else if (strcasecmp(argv[i], "--no-bind") == 0) {
 
            harness_game_config.no_bind = 1;
 
            handled = 1;
 
        }
 
 
 
        if (handled) {
 
            // shift args downwards
 
            for (int j = i; j < *argc - 1; j++) {
 
                argv[j] = argv[j + 1];
 
            }
 
            (*argc)--;
 
            i--;
 
        }
 
    }
 
 
 
    return 0;
 
}
 
 
 
// Render 2d back buffer
 
void Harness_RenderScreen(br_pixelmap* dst, br_pixelmap* src) {
 
    gHarness_platform.Renderer_FullScreenQuad((uint8_t*)src->pixels);
 
 
 
    last_dst = dst;
 
    last_src = src;
 
}
 
 
 
void Harness_Hook_BrV1dbRendererBegin(br_v1db_state* v1db) {
 
    renderer_state = NewHarnessBrRenderer();
 
    v1db->renderer = (br_renderer*)renderer_state;
 
}
 
 
 
static int Harness_CalculateFrameDelay(void) {
 
    if (harness_game_config.fps == 0) {
 
        return 0;
 
    }
 
 
 
    unsigned int now = GetTotalTime();
 
 
 
    if (last_frame_time != 0) {
 
        unsigned int frame_time = now - last_frame_time;
 
        last_frame_time = now;
 
        if (frame_time < 100) {
 
            int sleep_time = (1000 / harness_game_config.fps) - frame_time;
 
            if (sleep_time > 5) {
 
                return sleep_time;
 
            }
 
        }
 
    }
 
    return 0;
 
}
 
 
 
void Harness_Hook_renderActor(br_actor* actor, br_model* model, br_material* material, br_token type) {
 
    gHarness_platform.Renderer_Model(actor, model, material, type, renderer_state->state.matrix.model_to_view);
 
}
 
 
 
// Called by game to swap buffers at end of frame rendering
 
void Harness_Hook_BrPixelmapDoubleBuffer(br_pixelmap* dst, br_pixelmap* src) {
 
 
 
    // draw the current colour_buffer (2d screen) contents
 
    Harness_RenderScreen(dst, src);
 
 
 
    int delay_ms = Harness_CalculateFrameDelay();
 
    gHarness_platform.SwapWindow();
 
    if (delay_ms > 0) {
 
        gHarness_platform.Sleep(delay_ms);
 
    }
 
 
 
    gHarness_platform.Renderer_ClearBuffers();
 
    last_frame_time = GetTotalTime();
 
}
 
 
 
void Harness_RenderLastScreen(void) {
 
    if (last_dst) {
 
        Harness_RenderScreen(last_dst, last_src);
 
        gHarness_platform.SwapWindow();
 
    }
 
}
 
 
 
// Filesystem hooks
 
FILE* Harness_Hook_fopen(const char* pathname, const char* mode) {
 
    return OS_fopen(pathname, mode);
 
}