#include "world.h"
#include <stdlib.h>

#include "brender.h"
#include "brucetrk.h"
#include "car.h"
#include "depth.h"
#include "drmem.h"
#include "errors.h"
#include "finteray.h"
#include "flicplay.h"
#include "globvars.h"
#include "globvrpb.h"
#include "harness.h"
#include "loading.h"
#include "opponent.h"
#include "pd/sys.h"
#include "pedestrn.h"
#include "spark.h"
#include "utility.h"

#include <string.h>

br_actor* gActor_array[500];
float* gGroove_funk_bindings[960];
br_actor* gDelete_list[500];
br_actor* gLight_array[50];
br_model* gAdditional_models[1000];
br_actor* gSpec_vol_actors[100];
char* gDepth_effect_names[] = { "dark", "fog" };
char* gFunk_nature_names[] = { "constant", "distance", "lastlap", "otherlaps" };
char* gGroove_object_names[] = { "spin", "rock", "throb", "shear" };
char* gGroove_path_names[] = { "straight", "circular" };
char* gGroove_nature_names[] = { "constant", "distance" };
char* gFunk_anim_names[] = { "frames", "flic" };
char* gFunk_move_names[] = { "linear", "harmonic", "flash", "controlled", "absolute", "continuous" };
tPath_name gAdditional_actor_path;
char* gTime_mode_names[] = { "approximate", "accurate" };
tPath_name gAdditional_model_path;
tU32 gPrevious_groove_times[2];
int gRace_file_version;
br_vector3 gActor_centre;
tWall_texturing_level gWall_texturing_level = eWTL_full;
int gNumber_of_additional_models;
int gNumber_of_actors;
float x_0;
br_actor* gLast_actor;
br_actor* gKnown_actor;
br_actor* gAdditional_actors;
br_actor* gGroove_by_proxy_actor;
int gRendering_accessories;
tScale_mode gCurrent_scale_mode;
int gNumber_of_lights;
tRoad_texturing_level gRoad_texturing_level = eRTL_full;
int gDelete_count;
br_scalar gNearest_distance;
br_actor* gNearest_actor;
br_actor* gStandard_lamp;
tRotate_mode gCurrent_rotate_mode = eRotate_mode_y;
tCar_texturing_level gCar_texturing_level = eCTL_full;
br_scalar gSight_distance_squared;
char* gLollipop_names[] = { "xlollipop", "ylollipop", "zlollipop" };
char* gAxis_names[] = { "x", "y", "z" };
int gGroovidelics_array_size;
tGroovidelic_spec* gGroovidelics_array;
int gSpec_vol_mode;
char* gFunk_type_names[] = { "spin", "rock", "throb", "slither", "roll" };
tFunkotronic_spec* gFunkotronics_array;
int gFunkotronics_array_size;

// IDA: float __cdecl MapSawToTriangle(float pNumber)
float MapSawToTriangle(float pNumber) {
    LOG_TRACE("(%f)", pNumber);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl SetSightDistance(br_scalar pYon)
void SetSightDistance(br_scalar pYon) {
    gSight_distance_squared = pYon * 1.02f * (pYon * 1.02f);
}

// IDA: br_actor* __usercall FindActorInArray@<EAX>(char *pThe_name@<EAX>)
br_actor* FindActorInArray(char* pThe_name) {
    int i;
    LOG_TRACE("(\"%s\")", pThe_name);
    NOT_IMPLEMENTED();
}

// IDA: br_actor* __usercall FindLightInArray@<EAX>(char *pThe_name@<EAX>)
br_actor* FindLightInArray(char* pThe_name) {
    int i;
    LOG_TRACE("(\"%s\")", pThe_name);
    NOT_IMPLEMENTED();
}

// IDA: br_actor* __usercall CloneActor@<EAX>(br_actor *pSource_actor@<EAX>)
br_actor* CloneActor(br_actor* pSource_actor) {
    br_actor* new_actor;
    br_actor* child_actor;
    br_actor* new_child_actor;
    LOG_TRACE("(%p)", pSource_actor);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall InitialiseStorageSpace(tBrender_storage *pStorage_space@<EAX>, int pMax_pixelmaps@<EDX>, int pMax_shade_tables@<EBX>, int pMax_materials@<ECX>, int pMax_models)
void InitialiseStorageSpace(tBrender_storage* pStorage_space, int pMax_pixelmaps, int pMax_shade_tables, int pMax_materials, int pMax_models) {
    LOG_TRACE("(%p, %d, %d, %d, %d)", pStorage_space, pMax_pixelmaps, pMax_shade_tables, pMax_materials, pMax_models);
    pStorage_space->pixelmaps_count = 0;
    pStorage_space->shade_tables_count = 0;
    pStorage_space->materials_count = 0;
    pStorage_space->models_count = 0;
    pStorage_space->max_pixelmaps = pMax_pixelmaps;
    pStorage_space->max_shade_tables = pMax_shade_tables;
    pStorage_space->max_materials = pMax_materials;
    pStorage_space->max_models = pMax_models;
    pStorage_space->pixelmaps = BrMemCalloc(pMax_pixelmaps, 4, kMem_stor_space_pix);
    pStorage_space->shade_tables = BrMemCalloc(pMax_shade_tables, 4, kMem_stor_space_tab);
    pStorage_space->materials = BrMemCalloc(pMax_materials, 4, kMem_stor_space_mat);
    pStorage_space->models = BrMemCalloc(pMax_models, 4, kMem_stor_space_mod);
    pStorage_space->saved_colour_maps = BrMemCalloc(pMax_materials, 4, kMem_stor_space_save);
}

// IDA: void __usercall DisposeStorageSpace(tBrender_storage *pStorage_space@<EAX>)
void DisposeStorageSpace(tBrender_storage* pStorage_space) {
    BrMemFree(pStorage_space->pixelmaps);
    BrMemFree(pStorage_space->shade_tables);
    BrMemFree(pStorage_space->materials);
    BrMemFree(pStorage_space->models);
    BrMemFree(pStorage_space->saved_colour_maps);
}

// IDA: void __usercall ClearOutStorageSpace(tBrender_storage *pStorage_space@<EAX>)
void ClearOutStorageSpace(tBrender_storage* pStorage_space) {
    int i;
    LOG_TRACE("(%p)", pStorage_space);

    for (i = 0; pStorage_space->pixelmaps_count > i; ++i) {
        if (pStorage_space->pixelmaps[i]) {
            BrMapRemove(pStorage_space->pixelmaps[i]);
            BrPixelmapFree(pStorage_space->pixelmaps[i]);
        }
    }
    pStorage_space->pixelmaps_count = 0;
    for (i = 0; pStorage_space->shade_tables_count > i; ++i) {
        if (pStorage_space->shade_tables[i]) {
            BrTableRemove(pStorage_space->shade_tables[i]);
            BrPixelmapFree(pStorage_space->shade_tables[i]);
        }
    }
    pStorage_space->shade_tables_count = 0;
    for (i = 0; pStorage_space->materials_count > i; ++i) {
        if (pStorage_space->materials[i]) {
            BrMaterialRemove(pStorage_space->materials[i]);
            BrMaterialFree(pStorage_space->materials[i]);
        }
    }
    pStorage_space->materials_count = 0;
    for (i = 0; pStorage_space->models_count > i; ++i) {
        if (pStorage_space->models[i]) {
            BrModelRemove(pStorage_space->models[i]);
            BrModelFree(pStorage_space->models[i]);
        }
    }
    pStorage_space->models_count = 0;
}

// IDA: tAdd_to_storage_result __usercall AddPixelmapToStorage@<EAX>(tBrender_storage *pStorage_space@<EAX>, br_pixelmap **pThe_pm@<EDX>)
// This seems like the signature should be `br_pixelmap* pThe_pm`
tAdd_to_storage_result AddPixelmapToStorage(tBrender_storage* pStorage_space, br_pixelmap** pThe_pm) {
    int i;
    LOG_TRACE("(%p, %p)", pStorage_space, pThe_pm);

    if (pStorage_space->pixelmaps_count >= pStorage_space->max_pixelmaps) {
        return eStorage_not_enough_room;
    }

    for (i = 0; i < pStorage_space->pixelmaps_count; i++) {
        if (pStorage_space->pixelmaps[i]->identifier
            && ((br_pixelmap*)pThe_pm)->identifier
            && !strcmp(pStorage_space->pixelmaps[i]->identifier, ((br_pixelmap*)pThe_pm)->identifier)) {
            return eStorage_duplicate;
        }
    }
    pStorage_space->pixelmaps[pStorage_space->pixelmaps_count++] = (br_pixelmap*)pThe_pm;
    return eStorage_allocated;
}

// IDA: tAdd_to_storage_result __usercall AddShadeTableToStorage@<EAX>(tBrender_storage *pStorage_space@<EAX>, br_pixelmap *pThe_st@<EDX>)
tAdd_to_storage_result AddShadeTableToStorage(tBrender_storage* pStorage_space, br_pixelmap* pThe_st) {
    int i;
    LOG_TRACE("(%p, %p)", pStorage_space, pThe_st);

    if (pStorage_space->shade_tables_count >= pStorage_space->max_shade_tables) {
        return eStorage_not_enough_room;
    }

    for (i = 0; i < pStorage_space->shade_tables_count; i++) {
        if (pStorage_space->shade_tables[i]->identifier
            && pThe_st->identifier
            && !strcmp(pStorage_space->shade_tables[i]->identifier, pThe_st->identifier)) {
            return eStorage_duplicate;
        }
    }
    pStorage_space->shade_tables[pStorage_space->shade_tables_count++] = pThe_st;
    return eStorage_allocated;
}

// IDA: tAdd_to_storage_result __usercall AddMaterialToStorage@<EAX>(tBrender_storage *pStorage_space@<EAX>, br_material *pThe_mat@<EDX>)
tAdd_to_storage_result AddMaterialToStorage(tBrender_storage* pStorage_space, br_material* pThe_mat) {
    int i;
    LOG_TRACE("(%p, %p)", pStorage_space, pThe_mat);

    if (pStorage_space->materials_count >= pStorage_space->max_materials) {
        return eStorage_not_enough_room;
    }
    for (i = 0; i < pStorage_space->materials_count; i++) {
        if (pStorage_space->materials[i]->identifier
            && pThe_mat->identifier
            && !strcmp(pStorage_space->materials[i]->identifier, pThe_mat->identifier)) {
            return eStorage_duplicate;
        }
    }
    pStorage_space->saved_colour_maps[pStorage_space->materials_count] = 0;
    pStorage_space->materials[pStorage_space->materials_count++] = pThe_mat;
    return eStorage_allocated;
}

// IDA: tAdd_to_storage_result __usercall AddModelToStorage@<EAX>(tBrender_storage *pStorage_space@<EAX>, br_model *pThe_mod@<EDX>)
tAdd_to_storage_result AddModelToStorage(tBrender_storage* pStorage_space, br_model* pThe_mod) {
    int i;
    LOG_TRACE("(%p, %p)", pStorage_space, pThe_mod);

    if (pStorage_space->materials_count >= pStorage_space->max_models) {
        return eStorage_not_enough_room;
    }
    for (i = 0; i < pStorage_space->models_count; i++) {
        if (pStorage_space->models[i]
            && pStorage_space->models[i]->identifier
            && pThe_mod->identifier
            && !strcmp(pStorage_space->models[i]->identifier, pThe_mod->identifier)) {
            return eStorage_duplicate;
        }
    }
    pStorage_space->models[pStorage_space->models_count] = pThe_mod;
    pStorage_space->models_count++;
    return eStorage_allocated;
}

// IDA: int __usercall LoadNPixelmaps@<EAX>(tBrender_storage *pStorage_space@<EAX>, FILE *pF@<EDX>, int pCount@<EBX>)
int LoadNPixelmaps(tBrender_storage* pStorage_space, FILE* pF, int pCount) {
    tPath_name the_path;
    int i;
    int j;
    int new_ones;
    int total;
    char s[256];
    char* str;
    br_pixelmap* temp_array[200];
    LOG_TRACE("(%p, %p, %d)", pStorage_space, pF, pCount);

    new_ones = 0;
    for (i = 0; pCount > i; ++i) {
        PossibleService();
        GetALineAndDontArgue(pF, s);
        str = strtok(s, "\t ,/");
        PathCat(the_path, gApplication_path, gGraf_specs[gGraf_spec_index].data_dir_name);
        PathCat(the_path, the_path, "PIXELMAP");
        PathCat(the_path, the_path, str);
        AllowOpenToFail();
        total = DRPixelmapLoadMany(the_path, temp_array, 200);
        if (!total) {
            PathCat(the_path, gApplication_path, "PIXELMAP");
            PathCat(the_path, the_path, str);
            total = DRPixelmapLoadMany(the_path, temp_array, 200);
            if (!total) {
                FatalError(79);
            }
        }
        for (j = 0; j < total; j++) {
            if (temp_array[j]) {
                switch (AddPixelmapToStorage(pStorage_space, (br_pixelmap**)temp_array[j])) {
                case eStorage_not_enough_room:
                    FatalError(67);
                    break;
                case eStorage_duplicate:
                    BrPixelmapFree(temp_array[j]);
                    break;
                case eStorage_allocated:
                    BrMapAdd(temp_array[j]);
                    ++new_ones;
                    break;
                }
            }
        }
    }
    return new_ones;
}

// IDA: br_pixelmap* __usercall LoadSinglePixelmap@<EAX>(tBrender_storage *pStorage_space@<EAX>, char *pName@<EDX>)
br_pixelmap* LoadSinglePixelmap(tBrender_storage* pStorage_space, char* pName) {
    br_pixelmap* temp;
    LOG_TRACE("(%p, \"%s\")", pStorage_space, pName);

    temp = LoadPixelmap(pName);
    if (!temp) {
        return BrMapFind(pName);
    }

    switch (AddPixelmapToStorage(pStorage_space, (br_pixelmap**)temp)) {
    case eStorage_not_enough_room:
        FatalError(67);
        break;

    case eStorage_duplicate:
        BrPixelmapFree(temp);
        return BrMapFind(pName);

    case eStorage_allocated:
        BrMapAdd(temp);
        return temp;
    }

    return NULL;
}

// IDA: br_material* __usercall LoadSingleMaterial@<EAX>(tBrender_storage *pStorage_space@<EAX>, char *pName@<EDX>)
br_material* LoadSingleMaterial(tBrender_storage* pStorage_space, char* pName) {
    br_material* temp;
    LOG_TRACE("(%p, \"%s\")", pStorage_space, pName);

    temp = LoadMaterial(pName);
    if (!temp) {
        return BrMaterialFind(pName);
    }

    switch (AddMaterialToStorage(pStorage_space, temp)) {
    case eStorage_not_enough_room:
        FatalError(69);
        break;

    case eStorage_duplicate:
        BrMaterialFree(temp);
        return BrMaterialFind(pName);

    case eStorage_allocated:
        BrMaterialAdd(temp);
        return temp;
    }

    return NULL;
}

// IDA: int __usercall LoadNShadeTables@<EAX>(tBrender_storage *pStorage_space@<EAX>, FILE *pF@<EDX>, int pCount@<EBX>)
int LoadNShadeTables(tBrender_storage* pStorage_space, FILE* pF, int pCount) {
    tPath_name the_path;
    int i;
    int j;
    int new_ones;
    int total;
    char s[256];
    char* str;
    br_pixelmap* temp_array[50];
    LOG_TRACE("(%p, %p, %d)", pStorage_space, pF, pCount);

    new_ones = 0;
    for (i = 0; i < pCount; i++) {
        PossibleService();
        GetALineAndDontArgue(pF, s);
        str = strtok(s, "\t ,/");
        PathCat(the_path, gApplication_path, "SHADETAB");
        PathCat(the_path, the_path, str);
        total = DRPixelmapLoadMany(the_path, temp_array, 50);
        if (!total) {
            FatalError(80);
        }
        for (j = 0; j < total; j++) {
            if (temp_array[j]) {
                switch (AddShadeTableToStorage(pStorage_space, temp_array[j])) {
                case eStorage_not_enough_room:
                    FatalError(68);
                    break;

                case eStorage_duplicate:
                    BrPixelmapFree(temp_array[j]);
                    break;
                case eStorage_allocated:
                    BrTableAdd(temp_array[j]);
                    new_ones++;
                    break;
                }
            }
        }
    }
    return new_ones;
}

// IDA: br_pixelmap* __usercall LoadSingleShadeTable@<EAX>(tBrender_storage *pStorage_space@<EAX>, char *pName@<EDX>)
br_pixelmap* LoadSingleShadeTable(tBrender_storage* pStorage_space, char* pName) {
    br_pixelmap* temp;
    LOG_TRACE("(%p, \"%s\")", pStorage_space, pName);

    temp = LoadShadeTable(pName);
    if (!temp) {
        return BrTableFind(pName);
    }

    switch (AddShadeTableToStorage(pStorage_space, temp)) {
    case eStorage_not_enough_room:
        FatalError(68);
        break;

    case eStorage_duplicate:
        BrPixelmapFree(temp);
        return BrTableFind(pName);

    case eStorage_allocated:
        BrTableAdd(temp);
        return temp;
    }

    return NULL;
}

// IDA: int __usercall LoadNMaterials@<EAX>(tBrender_storage *pStorage_space@<EAX>, FILE *pF@<EDX>, int pCount@<EBX>)
int LoadNMaterials(tBrender_storage* pStorage_space, FILE* pF, int pCount) {
    tPath_name the_path;
    int i;
    int j;
    int new_ones;
    int total;
    char s[256];
    char* str;
    br_material* temp_array[200];
    LOG_TRACE("(%p, %p, %d)", pStorage_space, pF, pCount);

    new_ones = 0;
    for (i = 0; i < pCount; ++i) {
        PossibleService();
        GetALineAndDontArgue(pF, s);
        str = strtok(s, "\t ,/");
        PathCat(the_path, gApplication_path, "MATERIAL");
        PathCat(the_path, the_path, str);
        total = BrMaterialLoadMany(the_path, temp_array, 200);
        if (!total) {
            FatalError(81);
        }
        for (j = 0; j < total; j++) {
            if (temp_array[j]) {
                switch (AddMaterialToStorage(pStorage_space, temp_array[j])) {
                case eStorage_not_enough_room:
                    FatalError(69);
                    break;
                case eStorage_duplicate:
                    BrMaterialFree(temp_array[j]);
                    break;
                case eStorage_allocated:
                    BrMaterialAdd(temp_array[j]);
                    new_ones++;
                }
            }
        }
    }
    return new_ones;
}

// IDA: int __usercall LoadNModels@<EAX>(tBrender_storage *pStorage_space@<EAX>, FILE *pF@<EDX>, int pCount@<EBX>)
int LoadNModels(tBrender_storage* pStorage_space, FILE* pF, int pCount) {
    tPath_name the_path;
    int i;
    int j;
    int new_ones;
    int total;
    char s[256];
    char* str;
    br_model* temp_array[2000];
    v11model* prepared;
    int group;
    LOG_TRACE("(%p, %p, %d)", pStorage_space, pF, pCount);

    new_ones = 0;
    for (i = 0; i < pCount; i++) {
        PossibleService();
        GetALineAndDontArgue(pF, s);
        str = strtok(s, "\t ,/");
        PathCat(the_path, gApplication_path, "MODELS");
        PathCat(the_path, the_path, str);
        total = BrModelLoadMany(the_path, temp_array, 2000);
        if (!total) {
            FatalError(82);
        }
        for (j = 0; j < total; j++) {
            if (temp_array[j]) {
                switch (AddModelToStorage(pStorage_space, temp_array[j])) {
                case eStorage_not_enough_room:
                    FatalError(70);
                    break;
                case eStorage_duplicate:
                    BrModelFree(temp_array[j]);
                    break;
                case eStorage_allocated:
                    temp_array[j]->flags |= 0x80u;
                    RemoveDoubleSided(temp_array[j]);
                    BrModelAdd(temp_array[j]);
                    ++new_ones;
                    break;
                }
            }
        }
    }

    return new_ones;
}

// IDA: void __usercall DodgyModelUpdate(br_model *pM@<EAX>)
void DodgyModelUpdate(br_model* pM) {
    LOG_TRACE("(%p)", pM);

    BrResFree(pM->faces);
    BrResFree(pM->vertices);
    pM->nfaces = 0;
    pM->nvertices = 0;
    pM->faces = NULL;
    pM->vertices = NULL;
}

// IDA: br_material* __usercall SuffixedMaterial@<EAX>(br_material *pOld@<EAX>, char *pSuffix@<EDX>)
br_material* SuffixedMaterial(br_material* pOld, char* pSuffix) {
    br_material* new_mat;
    char* new_id;
    LOG_TRACE("(%p, \"%s\")", pOld, pSuffix);
    NOT_IMPLEMENTED();
}

// IDA: int __usercall FaceIsRoad@<EAX>(br_model *pModel@<EAX>, tU16 pFace@<EDX>)
int FaceIsRoad(br_model* pModel, tU16 pFace) {
    br_vector3 v0;
    br_vector3 v1;
    br_vector3 cross;
    LOG_TRACE("(%p, %d)", pModel, pFace);
    NOT_IMPLEMENTED();
}

// IDA: br_material* __usercall RoadPerspToUntex@<EAX>(br_model *pModel@<EAX>, tU16 pFace@<EDX>)
br_material* RoadPerspToUntex(br_model* pModel, tU16 pFace) {
    br_material* old_mat;
    br_material* new_mat;
    LOG_TRACE("(%p, %d)", pModel, pFace);
    NOT_IMPLEMENTED();
}

// IDA: br_material* __usercall WallPerspToLinear@<EAX>(br_model *pModel@<EAX>, tU16 pFace@<EDX>)
br_material* WallPerspToLinear(br_model* pModel, tU16 pFace) {
    br_material* old_mat;
    br_material* new_mat;
    LOG_TRACE("(%p, %d)", pModel, pFace);
    NOT_IMPLEMENTED();
}

// IDA: br_material* __usercall WallPerspToUntex@<EAX>(br_model *pModel@<EAX>, tU16 pFace@<EDX>)
br_material* WallPerspToUntex(br_model* pModel, tU16 pFace) {
    br_material* old_mat;
    br_material* new_mat;
    LOG_TRACE("(%p, %d)", pModel, pFace);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall ProcessModelFaceMaterials2(br_model *pModel@<EAX>, tPMFM2CB pCallback@<EDX>)
void ProcessModelFaceMaterials2(br_model* pModel, tPMFM2CB pCallback) {
    tU16 f;
    tU16 group;
    br_material* old_mat;
    LOG_TRACE("(%p, %d)", pModel, pCallback);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall ProcessModelFaceMaterials(br_model *pModel@<EAX>, tPMFMCB pCallback@<EDX>)
void ProcessModelFaceMaterials(br_model* pModel, tPMFMCB pCallback) {
    tU16 f;
    br_material* possible_mat;
    br_material* new_mat;
    LOG_TRACE("(%p, %d)", pModel, pCallback);
    NOT_IMPLEMENTED();
}

// IDA: int __usercall LoadNTrackModels@<EAX>(tBrender_storage *pStorage_space@<EAX>, FILE *pF@<EDX>, int pCount@<EBX>)
int LoadNTrackModels(tBrender_storage* pStorage_space, FILE* pF, int pCount) {
    tPath_name the_path;
    int i;
    int j;
    int new_ones;
    int total;
    int group;
    char s[256];
    char* str;
    br_model* temp_array[2000];
    v11model* prepared;
    LOG_TRACE("(%p, %p, %d)", pStorage_space, pF, pCount);

    new_ones = 0;
    for (i = 0; i < pCount; i++) {
        GetALineAndDontArgue(pF, s);
        str = strtok(s, "\t ,/");
        PathCat(the_path, gApplication_path, "MODELS");
        PathCat(the_path, the_path, str);
        total = BrModelLoadMany(the_path, temp_array, 2000);
        if (!total) {
            FatalError(82, str);
        }
        for (j = 0; j < total; j++) {
            if (temp_array[j]) {
                LOG_DEBUG("adding track model %s %d, %d", temp_array[j]->identifier, temp_array[j]->nfaces, temp_array[j]->nvertices);
                switch (AddModelToStorage(pStorage_space, temp_array[j])) {
                case eStorage_not_enough_room:
                    FatalError(70);
                    break;
                case eStorage_duplicate:
                    BrModelFree(temp_array[j]);
                    break;
                case eStorage_allocated:
                    temp_array[j]->flags |= BR_MODF_UPDATEABLE;
                    if (!gRoad_texturing_level) {
                        ProcessModelFaceMaterials(temp_array[j], (tPMFMCB*)RoadPerspToUntex);
                    }
                    if (gWall_texturing_level == 1) {
                        ProcessModelFaceMaterials(temp_array[j], (tPMFMCB*)WallPerspToLinear);
                    }
                    if (!gWall_texturing_level) {
                        ProcessModelFaceMaterials(temp_array[j], (tPMFMCB*)WallPerspToUntex);
                    }
                    //RemoveDoubleSided(temp_array[j]);
                    BrModelAdd(temp_array[j]);
                    LOG_DEBUG("added track model %s %d, %d", temp_array[j]->identifier, temp_array[j]->nfaces, temp_array[j]->nvertices);
                    new_ones++;
                }
            }
        }
    }
    return new_ones;
}

// IDA: void __usercall LoadSomePixelmaps(tBrender_storage *pStorage_space@<EAX>, FILE *pF@<EDX>)
void LoadSomePixelmaps(tBrender_storage* pStorage_space, FILE* pF) {
    tPath_name the_path;
    int i;
    int j;
    int count;
    int new_ones;
    char s[256];
    char* str;
    br_pixelmap* temp_array[200];
    LOG_TRACE("(%p, %p)", pStorage_space, pF);

    GetALineAndDontArgue(pF, s);
    str = strtok(s, "\t ,/");
    sscanf(str, "%d", &count);
    LoadNPixelmaps(pStorage_space, pF, count);
}

// IDA: void __usercall LoadSomeShadeTables(tBrender_storage *pStorage_space@<EAX>, FILE *pF@<EDX>)
void LoadSomeShadeTables(tBrender_storage* pStorage_space, FILE* pF) {
    tPath_name the_path;
    int i;
    int j;
    int count;
    int new_ones;
    char s[256];
    char* str;
    br_pixelmap* temp_array[50];
    LOG_TRACE("(%p, %p)", pStorage_space, pF);

    GetALineAndDontArgue(pF, s);
    str = strtok(s, "\t ,/");
    sscanf(str, "%d", &count);
    LoadNShadeTables(pStorage_space, pF, count);
}

// IDA: void __usercall LoadSomeMaterials(tBrender_storage *pStorage_space@<EAX>, FILE *pF@<EDX>)
void LoadSomeMaterials(tBrender_storage* pStorage_space, FILE* pF) {
    tPath_name the_path;
    int i;
    int j;
    int count;
    int new_ones;
    char s[256];
    char* str;
    br_material* temp_array[200];
    LOG_TRACE("(%p, %p)", pStorage_space, pF);

    GetALineAndDontArgue(pF, s);
    str = strtok(s, "\t ,/");
    sscanf(str, "%d", &count);
    LoadNMaterials(pStorage_space, pF, count);
}

// IDA: void __usercall LoadSomeModels(tBrender_storage *pStorage_space@<EAX>, FILE *pF@<EDX>)
void LoadSomeModels(tBrender_storage* pStorage_space, FILE* pF) {
    tPath_name the_path;
    int i;
    int j;
    int count;
    int new_ones;
    char s[256];
    char* str;
    br_model* temp_array[2000];
    LOG_TRACE("(%p, %p)", pStorage_space, pF);

    GetALineAndDontArgue(pF, s);
    str = strtok(s, "\t ,/");
    sscanf(str, "%d", &count);
    LoadNModels(pStorage_space, pF, count);
}

// IDA: void __usercall LoadSomeTrackModels(tBrender_storage *pStorage_space@<EAX>, FILE *pF@<EDX>)
void LoadSomeTrackModels(tBrender_storage* pStorage_space, FILE* pF) {
    tPath_name the_path;
    int i;
    int j;
    int count;
    int new_ones;
    char s[256];
    char* str;
    br_model* temp_array[2000];
    LOG_TRACE("(%p, %p)", pStorage_space, pF);

    GetALineAndDontArgue(pF, s);
    str = strtok(s, "\t ,/");
    sscanf(str, "%d", &count);
    LoadNTrackModels(pStorage_space, pF, count);
}

// IDA: void __usercall AddFunkGrooveBinding(int pSlot_number@<EAX>, float *pPeriod_address@<EDX>)
void AddFunkGrooveBinding(int pSlot_number, float* pPeriod_address) {
    LOG_TRACE("(%d, %p)", pSlot_number, pPeriod_address);

    if (pSlot_number < 0 || pSlot_number >= COUNT_OF(gGroove_funk_bindings)) {
        FatalError(72);
    }
    gGroove_funk_bindings[pSlot_number] = pPeriod_address;
    *pPeriod_address = 0.0;
}

// IDA: void __usercall ControlBoundFunkGroove(int pSlot_number@<EAX>, float pValue)
void ControlBoundFunkGroove(int pSlot_number, float pValue) {
    LOG_TRACE("(%d, %f)", pSlot_number, pValue);
    NOT_IMPLEMENTED();
}

// IDA: float __usercall ControlBoundFunkGroovePlus@<ST0>(int pSlot_number@<EAX>, float pValue)
float ControlBoundFunkGroovePlus(int pSlot_number, float pValue) {
    LOG_TRACE("(%d, %f)", pSlot_number, pValue);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall ShiftBoundGrooveFunks(char *pStart@<EAX>, char *pEnd@<EDX>, int pDelta@<EBX>)
void ShiftBoundGrooveFunks(char* pStart, char* pEnd, int pDelta) {
    int i;
    LOG_TRACE("(\"%s\", \"%s\", %d)", pStart, pEnd, pDelta);

    for (i = 0; i < COUNT_OF(gGroove_funk_bindings); i++) {
        if ((intptr_t)gGroove_funk_bindings[i] >= (intptr_t)pStart
            && (intptr_t)gGroove_funk_bindings[i] < (intptr_t)pEnd) {
            gGroove_funk_bindings[i] = (float*)((char*)gGroove_funk_bindings[i] + (pDelta & 0xFFFFFFFC));
        }
    }
}

// IDA: tFunkotronic_spec* __cdecl AddNewFunkotronic()
tFunkotronic_spec* AddNewFunkotronic() {
    void* new_array;
    int i;
    LOG_TRACE("()");

    for (i = 0; i < gFunkotronics_array_size; i++) {
        if (gFunkotronics_array[i].owner == -999) {
            memset(&gFunkotronics_array[i], 0, sizeof(tFunkotronic_spec));
            return &gFunkotronics_array[i];
        }
    }
    gFunkotronics_array_size += 16;
    new_array = BrMemCalloc(gFunkotronics_array_size, sizeof(tFunkotronic_spec), kMem_funk_spec);
    if (gFunkotronics_array) {
        memcpy(new_array, gFunkotronics_array, sizeof(tFunkotronic_spec) * (gFunkotronics_array_size - 16));
        ShiftBoundGrooveFunks(
            (char*)gFunkotronics_array,
            (char*)&gFunkotronics_array[gFunkotronics_array_size - 16],
            (char*)new_array - (char*)gFunkotronics_array);
        BrMemFree(gFunkotronics_array);
    }
    gFunkotronics_array = new_array;
    for (i = 0; i < 16; i++) {
        gFunkotronics_array[gFunkotronics_array_size - i].owner = -999;
    }
    return &gFunkotronics_array[gFunkotronics_array_size - 16];
}

// IDA: void __usercall DisposeFunkotronics(int pOwner@<EAX>)
void DisposeFunkotronics(int pOwner) {
    int i;
    tFunkotronic_spec* the_funk;
    LOG_TRACE("(%d)", pOwner);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall AddProximityVertex(br_vector3 *pV@<EAX>, tFunkotronic_spec *pThe_funk@<EDX>)
void AddProximityVertex(br_vector3* pV, tFunkotronic_spec* pThe_funk) {
    LOG_TRACE("(%p, %p)", pV, pThe_funk);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl AddProximityVertexXYZ(br_scalar pX, br_scalar pY, br_scalar pZ, tFunkotronic_spec *pThe_funk)
void AddProximityVertexXYZ(br_scalar pX, br_scalar pY, br_scalar pZ, tFunkotronic_spec* pThe_funk) {
    br_vector3 v;
    LOG_TRACE("(%f, %f, %f, %p)", pX, pY, pZ, pThe_funk);
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __usercall CalcProximities@<EAX>(br_actor *pActor@<EAX>, br_material *pMat@<EDX>, tFunkotronic_spec *pThe_funk@<EBX>)
br_uint_32 CalcProximities(br_actor* pActor, br_material* pMat, tFunkotronic_spec* pThe_funk) {
    br_face* the_face;
    int i;
    LOG_TRACE("(%p, %p, %p)", pActor, pMat, pThe_funk);
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __usercall AddProximities@<EAX>(br_actor *pActor@<EAX>, br_material *pMat@<EDX>, tFunkotronic_spec *pThe_funk@<EBX>)
br_uint_32 AddProximities(br_actor* pActor, br_material* pMat, tFunkotronic_spec* pThe_funk) {
    br_face* the_face;
    int i;
    LOG_TRACE("(%p, %p, %p)", pActor, pMat, pThe_funk);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall Adjust2FloatsForExceptions(float *pVictim1@<EAX>, float *pVictim2@<EDX>, br_pixelmap *pCulprit@<EBX>)
void Adjust2FloatsForExceptions(float* pVictim1, float* pVictim2, br_pixelmap* pCulprit) {
    tException_list e;
    LOG_TRACE("(%p, %p, %p)", pVictim1, pVictim2, pCulprit);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall AddFunkotronics(FILE *pF@<EAX>, int pOwner@<EDX>, int pRef_offset@<EBX>)
void AddFunkotronics(FILE* pF, int pOwner, int pRef_offset) {
    char s[256];
    char* str;
    int first_time;
    int i;
    int j;
    tFunkotronic_spec* the_funk;
    float speed1;
    float speed2;
    float a_min;
    float d_min;
    float s_min;
    float a_max;
    float d_max;
    float s_max;
    void* the_pixels;
    br_pixelmap* the_pixelmap;
    float x_0;
    float x_1;
    int d_0;
    int d_1;
    LOG_TRACE("(%p, %d, %d)", pF, pOwner, pRef_offset);

    first_time = 1;
    while (!feof(pF)) {
        PossibleService();
        GetALineAndDontArgue(pF, s);
        if (strcmp(s, "END OF FUNK") == 0) {
            break;
        }

        if (!first_time) {
            if (strcmp(s, "NEXT FUNK") != 0) {
                FatalError(62);
            }
            GetALineAndDontArgue(pF, s);
        }
        first_time = 0;

        the_funk = AddNewFunkotronic();
        the_funk->owner = pOwner;
        str = strtok(s, "\t ,/");
        the_funk->material = BrMaterialFind(str);
        if (!the_funk->material) {
            FatalError(64);
        }
        the_funk->mode = GetALineAndInterpretCommand(pF, gFunk_nature_names, COUNT_OF(gFunk_nature_names));
        the_funk->matrix_mod_type = GetALineAndInterpretCommand(pF, gFunk_type_names, COUNT_OF(gFunk_type_names));
        if (the_funk->matrix_mod_type != -1) {
            the_funk->matrix_mode = GetALineAndInterpretCommand(pF, gFunk_move_names, COUNT_OF(gFunk_move_names));
        }
        switch (the_funk->matrix_mod_type) {
        case eMatrix_mod_spin:
            if (the_funk->matrix_mode != eMove_controlled && the_funk->matrix_mode != eMove_absolute) {
                x_0 = GetAFloat(pF);
                the_funk->matrix_mod_data.spin_info.period = x_0 == -1 ? 0.0 : 1000.0 / x_0;
            } else {
                i = GetAnInt(pF);
                AddFunkGrooveBinding(i + pRef_offset, &the_funk->matrix_mod_data.spin_info.period);
            }
            break;
        case eMatrix_mod_rock: // rock
            if (the_funk->matrix_mode != eMove_controlled && the_funk->matrix_mode != eMove_absolute) {
                x_0 = GetAFloat(pF);
                the_funk->matrix_mod_data.rock_info.period = x_0 == -1 ? 0.0 : 1000.0 / x_0;
            } else {
                d_0 = GetAnInt(pF);
                AddFunkGrooveBinding(d_0 + pRef_offset, &the_funk->matrix_mod_data.rock_info.period);
            }

            the_funk->matrix_mod_data.rock_info.rock_angle = GetAFloat(pF);
            GetPairOfFloats(pF, &x_0, &x_1);
            the_funk->matrix_mod_data.rock_info.x_centre = x_0 / 100.0;
            the_funk->matrix_mod_data.rock_info.y_centre = x_1 / 100.0;
            break;
        case eMatrix_mod_throb: // throb
            if (the_funk->matrix_mode != eMove_controlled && the_funk->matrix_mode != eMove_absolute) {
                GetPairOfFloats(pF, &speed1, &speed2);
                the_funk->matrix_mod_data.throb_info.x_period = speed1 == 0.0f ? 0.0f : 1000.0f / speed1;
                the_funk->matrix_mod_data.throb_info.y_period = speed2 == 0.0f ? 0.0f : 1000.0f / speed2;
            } else {
                GetPairOfInts(pF, &d_0, &d_1);
                if (d_0 >= 0) {
                    AddFunkGrooveBinding(d_0 + pRef_offset, &the_funk->matrix_mod_data.throb_info.x_period);
                }
                if (d_1 >= 0) {
                    AddFunkGrooveBinding(d_1 + pRef_offset, &the_funk->matrix_mod_data.throb_info.y_period);
                }
            }
            GetPairOfFloatPercents(
                pF,
                &the_funk->matrix_mod_data.throb_info.x_magnitude,
                &the_funk->matrix_mod_data.throb_info.y_magnitude);
            GetPairOfFloats(pF, &x_0, &x_1);
            the_funk->matrix_mod_data.throb_info.x_centre = x_0 / 100.0;
            the_funk->matrix_mod_data.throb_info.y_centre = x_1 / 100.0;
            if (the_funk->matrix_mode != eMove_controlled) {
                if (the_funk->matrix_mod_data.throb_info.x_period == 0.0) {
                    the_funk->matrix_mod_data.throb_info.x_period = 1.0;
                    the_funk->matrix_mod_data.throb_info.x_magnitude = 0.0;
                }
                if (the_funk->matrix_mod_data.throb_info.y_period == 0.0) {
                    the_funk->matrix_mod_data.throb_info.y_period = 1.0;
                    the_funk->matrix_mod_data.throb_info.y_magnitude = 0.0;
                }
            }
            break;
        case eMatrix_mod_slither: // slither
            if (the_funk->matrix_mode != eMove_controlled && the_funk->matrix_mode != eMove_absolute) {
                GetPairOfFloats(pF, &speed1, &speed2);
                the_funk->matrix_mod_data.slither_info.x_period = speed1 == 0.0f ? 0.0f : 1000.0f / speed1;
                the_funk->matrix_mod_data.slither_info.y_period = speed2 == 0.0f ? 0.0f : 1000.0f / speed2;
            } else {
                GetPairOfInts(pF, &d_0, &d_1);
                if (d_0 >= 0) {
                    AddFunkGrooveBinding(d_0 + pRef_offset, &the_funk->matrix_mod_data.slither_info.x_period);
                }
                if (d_1 >= 0) {
                    AddFunkGrooveBinding(d_1 + pRef_offset, &the_funk->matrix_mod_data.slither_info.y_period);
                }
            }
            GetPairOfFloatPercents(
                pF,
                &the_funk->matrix_mod_data.slither_info.x_magnitude,
                &the_funk->matrix_mod_data.slither_info.y_magnitude);
            if (the_funk->matrix_mode != eMove_controlled) {
                if (the_funk->matrix_mod_data.slither_info.x_period == 0.0) {
                    the_funk->matrix_mod_data.slither_info.x_period = 1.0;
                    the_funk->matrix_mod_data.slither_info.x_magnitude = 0.0;
                }
                if (the_funk->matrix_mod_data.slither_info.y_period == 0.0) {
                    the_funk->matrix_mod_data.slither_info.y_period = 1.0;
                    the_funk->matrix_mod_data.slither_info.y_magnitude = 0.0;
                }
            }
            break;
        case eMatrix_mod_roll: // roll
            if (the_funk->matrix_mode != eMove_controlled && the_funk->matrix_mode != eMove_absolute) {
                GetPairOfFloats(pF, &speed1, &speed2);
                the_funk->matrix_mod_data.roll_info.x_period = speed1 == 0.0f ? 0.0f : 1000.0f / speed1;
                the_funk->matrix_mod_data.roll_info.y_period = speed2 == 0.0f ? 0.0f : 1000.0f / speed2;
            } else {
                GetPairOfInts(pF, &d_0, &d_1);
                if (d_0 >= 0) {
                    AddFunkGrooveBinding(d_0 + pRef_offset, &the_funk->matrix_mod_data.roll_info.x_period);
                }
                if (d_1 >= 0) {
                    AddFunkGrooveBinding(d_1 + pRef_offset, &the_funk->matrix_mod_data.roll_info.y_period);
                }
            }
            break;
        default:
            break;
        }
        the_funk->lighting_animation_type = GetALineAndInterpretCommand(pF, gFunk_move_names, COUNT_OF(gFunk_move_names));
        if (the_funk->lighting_animation_type != -1) {
            if (the_funk->lighting_animation_type != eMove_controlled && the_funk->lighting_animation_type != eMove_absolute) {
                x_0 = GetAFloat(pF);
                the_funk->lighting_animation_period = x_0 == 0.0f ? 0.0f : 1000.0 / x_0;
            } else {
                d_0 = GetAnInt(pF);
                AddFunkGrooveBinding(d_0 + pRef_offset, &the_funk->lighting_animation_period);
            }
            GetThreeFloatPercents(pF, &a_min, &d_min, &s_min);
            GetThreeFloatPercents(pF, &a_max, &d_max, &s_max);
            the_funk->ambient_base = (a_min + a_max) / 2.0;
            the_funk->direct_base = (d_min + d_max) / 2.0;
            the_funk->specular_base = (s_min + s_max) / 2.0;
            the_funk->ambient_delta = (a_max - a_min) / 2.0;
            the_funk->direct_delta = (d_max - d_min) / 2.0;
            the_funk->specular_delta = (s_max - s_min) / 2.0;
        }
        the_funk->texture_animation_type = GetALineAndInterpretCommand(pF, gFunk_anim_names, COUNT_OF(gFunk_anim_names));
        if (the_funk->texture_animation_type != -1) {
            the_funk->time_mode = GetALineAndInterpretCommand(pF, gTime_mode_names, COUNT_OF(gTime_mode_names));
        }
        if (the_funk->texture_animation_type == eTexture_animation_flic && gAusterity_mode) {
            the_funk->texture_animation_type = -1;
            GetALineAndDontArgue(pF, s);
        }
        the_funk->last_frame = 0.0;

        if (the_funk->texture_animation_type == eTexture_animation_flic) {
            GetAString(pF, s);
            the_funk->texture_animation_data.flic_info.flic_data = 0;
            if (LoadFlicData(
                    s,
                    &the_funk->texture_animation_data.flic_info.flic_data,
                    &the_funk->texture_animation_data.flic_info.flic_data_length)) {
                the_funk->texture_animation_data.flic_info.flic_descriptor.data_start = NULL;
                StartFlic(
                    s,
                    -1,
                    &the_funk->texture_animation_data.flic_info.flic_descriptor,
                    the_funk->texture_animation_data.flic_info.flic_data_length,
                    (tS8*)the_funk->texture_animation_data.flic_info.flic_data,
                    0,
                    0,
                    0,
                    0);
                the_funk->last_frame = 0.0;
                the_pixels = BrMemAllocate(
                    the_funk->texture_animation_data.flic_info.flic_descriptor.height
                        * ((the_funk->texture_animation_data.flic_info.flic_descriptor.width + 3) & 0xFFFFFFFC),
                    kMem_video_pixels);
                if (gScreen->row_bytes < 0) {
                    BrFatal(
                        "C:\\Msdev\\Projects\\DethRace\\World.c",
                        1729,
                        "Bruce bug at line %d, file C:\\Msdev\\Projects\\DethRace\\World.c",
                        193);
                }
                the_pixelmap = DRPixelmapAllocate(
                    gScreen->type,
                    the_funk->texture_animation_data.flic_info.flic_descriptor.width,
                    the_funk->texture_animation_data.flic_info.flic_descriptor.height,
                    the_pixels,
                    0);
                AssertFlicPixelmap(&the_funk->texture_animation_data.flic_info.flic_descriptor, the_pixelmap);
                the_funk->material->colour_map = the_pixelmap;
                BrMaterialUpdate(the_funk->material, 0x7FFFu);
            } else {
                the_funk->texture_animation_type = -1;
            }
        } else if (the_funk->texture_animation_type == eTexture_animation_frames) {
            i = GetALineAndInterpretCommand(pF, gFunk_move_names, COUNT_OF(gFunk_move_names));
            the_funk->texture_animation_data.frames_info.mode = i;
            if (the_funk->texture_animation_data.frames_info.mode != eMove_controlled
                && the_funk->texture_animation_data.frames_info.mode != eMove_absolute) {
                x_0 = GetAFloat(pF);
                the_funk->texture_animation_data.frames_info.period = x_0 == 0.0f ? 0.0f : 1000.0 / x_0;
            } else {
                d_0 = GetAnInt(pF);
                AddFunkGrooveBinding(d_0 + pRef_offset, &the_funk->texture_animation_data.frames_info.period);
            }

            the_funk->texture_animation_data.frames_info.texture_count = (int)GetAFloat(pF);
            for (i = 0; i < the_funk->texture_animation_data.frames_info.texture_count; i++) {
                GetAString(pF, s);
                the_funk->texture_animation_data.frames_info.textures[i] = BrMapFind(s);
                if (!the_funk->texture_animation_data.frames_info.textures[i]) {
                    FatalError(66);
                }
            }
        }
        the_funk->proximity_count = 0;
        the_funk->proximity_array = 0;
        if (the_funk->mode == eFunk_mode_distance) {
            DRActorEnumRecurseWithMat(gUniverse_actor, 0, (recurse_with_mat_cbfn*)CalcProximities, the_funk);
            the_funk->proximity_array = BrMemAllocate(sizeof(br_vector3) * the_funk->proximity_count, kMem_funk_prox_array);
            the_funk->proximity_count = 0;
            DRActorEnumRecurseWithMat(gUniverse_actor, 0, (recurse_with_mat_cbfn*)AddProximities, the_funk);
            for (i = 0; i < the_funk->proximity_count; i++) {
                for (j = i + 1; the_funk->proximity_count > j; ++j) {
                    if (the_funk->proximity_array[j].v[0] == the_funk->proximity_array[i].v[0]
                        && the_funk->proximity_array[j].v[1] == the_funk->proximity_array[i].v[1]
                        && the_funk->proximity_array[j].v[2] == the_funk->proximity_array[i].v[2]) {
                        memcpy(
                            &the_funk->proximity_array[j],
                            &the_funk->proximity_array[j + 1],
                            4 * (3 * (the_funk->proximity_count - j) - 3));
                        --j;
                        --the_funk->proximity_count;
                    }
                }
            }
        }
    }
}

// IDA: void __usercall DisposeGroovidelics(int pOwner@<EAX>)
void DisposeGroovidelics(int pOwner) {
    int i;
    tGroovidelic_spec* the_groove;
    LOG_TRACE("(%d)", pOwner);
    NOT_IMPLEMENTED();
}

// IDA: tGroovidelic_spec* __cdecl AddNewGroovidelic()
tGroovidelic_spec* AddNewGroovidelic() {
    void* new_array;
    int i;
    LOG_TRACE("()");

    for (i = 0; i < gGroovidelics_array_size; i++) {
        if (gGroovidelics_array[i].owner == -999) {
            memset(&gGroovidelics_array[i], 0, sizeof(tGroovidelic_spec));
            return &gGroovidelics_array[i];
        }
    }
    gGroovidelics_array_size += 16;
    new_array = BrMemCalloc(gGroovidelics_array_size, sizeof(tGroovidelic_spec), kMem_groove_spec);
    if (gGroovidelics_array) {
        memcpy(new_array, gGroovidelics_array, (gGroovidelics_array_size - 16) * sizeof(tGroovidelic_spec));
        ShiftBoundGrooveFunks(
            (char*)gGroovidelics_array,
            (char*)&gGroovidelics_array[gGroovidelics_array_size - 16],
            (char*)new_array - (char*)gGroovidelics_array);
        BrMemFree(gGroovidelics_array);
    }
    gGroovidelics_array = new_array;
    for (i = 0; i < 16; i++) {
        gGroovidelics_array[i + gGroovidelics_array_size - 16].owner = -999;
    }
    return &gGroovidelics_array[gGroovidelics_array_size - 16];
}

// IDA: void __usercall AddGroovidelics(FILE *pF@<EAX>, int pOwner@<EDX>, br_actor *pParent_actor@<EBX>, int pRef_offset@<ECX>, int pAllowed_to_be_absent)
void AddGroovidelics(FILE* pF, int pOwner, br_actor* pParent_actor, int pRef_offset, int pAllowed_to_be_absent) {
    char s[256];
    char* str;
    int first_time;
    int i;
    int j;
    tGroovidelic_spec* the_groove;
    float x_0;
    float x_1;
    float x_2;
    int d_0;
    int d_1;
    int d_2;
    LOG_TRACE("(%p, %d, %p, %d, %d)", pF, pOwner, pParent_actor, pRef_offset, pAllowed_to_be_absent);

    first_time = 1;

    while (!(feof(pF))) {
        PossibleService();
        GetALineAndDontArgue(pF, s);
        if (strcmp(s, "END OF GROOVE") == 0) {
            break;
        }

        if (!first_time) {
            if (strcmp(s, "NEXT GROOVE") != 0) {
                FatalError(63);
            }
            GetALineAndDontArgue(pF, s);
        }
        first_time = 0;
        str = strtok(s, "\t ,/");
        the_groove = AddNewGroovidelic();
        the_groove->owner = pOwner;
        the_groove->actor = DRActorFindRecurse(pParent_actor, str);

        if (!the_groove->actor) {
            if (!pAllowed_to_be_absent && !gAusterity_mode) {
                FatalError(65, str);
            }
            if (!gGroove_by_proxy_actor) {
                gGroove_by_proxy_actor = BrActorAllocate(BR_ACTOR_MODEL, 0);
                gGroove_by_proxy_actor->model = LoadModel("PROXY.DAT");
                BrModelAdd(gGroove_by_proxy_actor->model);
                BrActorAdd(gDont_render_actor, gGroove_by_proxy_actor);
            }
            the_groove->actor = gGroove_by_proxy_actor;
        }
        the_groove->lollipop_mode = GetALineAndInterpretCommand(pF, gLollipop_names, COUNT_OF(gLollipop_names));
        the_groove->mode = GetALineAndInterpretCommand(pF, gGroove_nature_names, COUNT_OF(gGroove_nature_names));
        ;
        the_groove->path_type = GetALineAndInterpretCommand(pF, gGroove_path_names, COUNT_OF(gGroove_path_names));
        the_groove->path_interrupt_status = 0;
        the_groove->object_interrupt_status = 0;
        if (the_groove->path_type != -1) {
            the_groove->path_mode = GetALineAndInterpretCommand(pF, gFunk_move_names, COUNT_OF(gFunk_move_names));
        }

        if (the_groove->path_type == eGroove_path_circular) {
            TELL_ME_IF_WE_PASS_THIS_WAY();
            // GetThreeFloats(pF, &v90, &v89, &v88);
            // the_groove->path_data.circular_info.centre.v[0] = v90;
            // the_groove->path_data.circular_info.centre.v[1] = v89;
            // the_groove->path_data.circular_info.centre.v[2] = v88;
            // if (the_groove->path_data.circular_info.centre.v[2] == 0.0
            //     && *(float*)&the_groove->path_data.circular_info.axis == 0.0
            //     && *((float*)&the_groove->path_data.circular_info + 6) == 0.0) {
            //     v25 = (_DWORD*)the_groove->actor->t.t.translate.t.v;
            //     v26 = (_DWORD*)&the_groove->path_data.circular_info.centre.v[2];
            //     *v26 = *v25;
            //     v26[1] = v25[1];
            //     v26[2] = v25[2];
            // }
            // if (the_groove->path_mode != 3 && the_groove->path_mode != 4) {
            //     v29 = GetAFloat(pF);
            //     x_0 = v29;
            //     if (v31) {
            //         v62 = 0.0;
            //     } else {
            //         v62 = 1000.0 / x_0;
            //     }
            //     the_groove->path_data.circular_info.period = v62;
            // } else {
            //     v27 = &the_groove->path_data.circular_info.period;
            //     v28 = GetAnInt(pF);
            //     AddFunkGrooveBinding(v28 + pRef_offset, v27);
            // }
            // v32 = GetAFloat(pF);
            // the_groove->path_data.circular_info.radius = v32;
            // v33 = GetALineAndInterpretCommand(pF, gAxis_names, 3);
            // the_groove->path_data.circular_info.axis = v33;

        } else if (the_groove->path_type == eGroove_path_straight) {
            GetThreeFloats(pF,
                &the_groove->path_data.straight_info.centre.v[0],
                &the_groove->path_data.straight_info.centre.v[1],
                &the_groove->path_data.straight_info.centre.v[2]);

            if (the_groove->path_data.straight_info.centre.v[0] == 0.0
                && the_groove->path_data.straight_info.centre.v[1] == 0.0
                && the_groove->path_data.straight_info.centre.v[2] == 0.0) {

                the_groove->path_data.straight_info.centre.v[0] = the_groove->actor->t.t.translate.t.v[0];
                the_groove->path_data.straight_info.centre.v[1] = the_groove->actor->t.t.translate.t.v[1];
                the_groove->path_data.straight_info.centre.v[2] = the_groove->actor->t.t.translate.t.v[2];
            }
            if (the_groove->path_mode != eMove_controlled && the_groove->path_mode != eMove_absolute) {
                x_0 = GetAFloat(pF);
                the_groove->path_data.straight_info.period = x_0 == 0.0f ? 0.0f : 1000.0 / x_0;
            } else {
                i = GetAnInt(pF);
                AddFunkGrooveBinding(i + pRef_offset, &the_groove->path_data.straight_info.period);
            }
            GetThreeFloats(
                pF,
                &the_groove->path_data.straight_info.x_delta,
                &the_groove->path_data.straight_info.y_delta,
                &the_groove->path_data.straight_info.z_delta);
        }
        the_groove->object_type = GetALineAndInterpretCommand(pF, gGroove_object_names, COUNT_OF(gGroove_object_names));
        the_groove->object_position = the_groove->actor->t.t.translate.t;
        if (the_groove->object_type != -1) {
            the_groove->object_mode = GetALineAndInterpretCommand(pF, gFunk_move_names, COUNT_OF(gFunk_move_names));
        }
        switch (the_groove->object_type) {
        case eGroove_object_spin:
            if (the_groove->object_mode != eMove_controlled && the_groove->object_mode != eMove_absolute) {
                x_0 = GetAFloat(pF);
                the_groove->object_data.spin_info.period = x_0 == 0.0f ? 0.0f : 1000.0 / x_0;
            } else {
                d_0 = GetAnInt(pF);
                AddFunkGrooveBinding(d_0 + pRef_offset, &the_groove->object_data.spin_info.period);
            }
            GetThreeFloats(pF,
                &the_groove->object_centre.v[0],
                &the_groove->object_centre.v[1],
                &the_groove->object_centre.v[2]);
            the_groove->object_data.spin_info.axis = GetALineAndInterpretCommand(pF, gAxis_names, COUNT_OF(gAxis_names));
            break;
        case eGroove_object_rock:
            if (the_groove->object_mode != eMove_controlled && the_groove->object_mode != eMove_absolute) {
                x_0 = GetAFloat(pF);
                the_groove->object_data.rock_info.period = x_0 == 0.0f ? 0.0f : 1000.0 / x_0;
            } else {
                d_0 = GetAnInt(pF);
                AddFunkGrooveBinding(d_0 + pRef_offset, &the_groove->object_data.rock_info.period);
            }
            GetThreeFloats(pF,
                &the_groove->object_centre.v[0],
                &the_groove->object_centre.v[1],
                &the_groove->object_centre.v[2]);
            the_groove->object_data.rock_info.axis = GetALineAndInterpretCommand(pF, gAxis_names, COUNT_OF(gAxis_names));
            the_groove->object_data.rock_info.max_angle = GetAFloat(pF);
            break;
        case eGroove_object_throb:
            if (the_groove->object_mode != eMove_controlled && the_groove->object_mode != eMove_absolute) {
                GetThreeFloats(pF, &x_0, &x_1, &x_2);
                the_groove->object_data.throb_info.x_period = x_0 == 0.0f ? 0.0f : 1000.0 / x_0;
                the_groove->object_data.throb_info.y_period = x_1 == 0.0f ? 0.0f : 1000.0 / x_1;
                the_groove->object_data.throb_info.z_period = x_2 == 0.0f ? 0.0f : 1000.0 / x_2;
            } else {
                GetThreeInts(pF, &d_0, &d_1, &d_2);
                if (d_0 >= 0) {
                    AddFunkGrooveBinding(d_0 + pRef_offset, &the_groove->object_data.throb_info.x_period);
                }
                if (d_1 >= 0) {
                    AddFunkGrooveBinding(d_1 + pRef_offset, &the_groove->object_data.throb_info.y_period);
                }
                if (d_2 >= 0) {
                    AddFunkGrooveBinding(d_2 + pRef_offset, &the_groove->object_data.throb_info.z_period);
                }
            }
            GetThreeFloats(pF,
                &the_groove->object_centre.v[0],
                &the_groove->object_centre.v[1],
                &the_groove->object_centre.v[2]);
            GetThreeFloatPercents(
                pF,
                &the_groove->object_data.throb_info.x_magnitude,
                &the_groove->object_data.throb_info.y_magnitude,
                &the_groove->object_data.throb_info.z_magnitude);
            break;
        case eGroove_object_shear:
            if (the_groove->object_mode != eMove_controlled && the_groove->object_mode != eMove_absolute) {
                GetThreeFloats(pF, &x_0, &x_1, &x_2);
                the_groove->object_data.shear_info.x_period = x_0 == 0.0f ? 0.0f : 1000.0 / x_0;
                the_groove->object_data.shear_info.y_period = x_1 == 0.0f ? 0.0f : 1000.0 / x_1;
                the_groove->object_data.shear_info.z_period = x_2 == 0.0f ? 0.0f : 1000.0 / x_2;
            } else {
                GetThreeInts(pF, &d_0, &d_1, &d_2);
                if (d_0 >= 0) {
                    AddFunkGrooveBinding(d_0 + pRef_offset, &the_groove->object_data.shear_info.x_period);
                }
                if (d_1 >= 0) {
                    AddFunkGrooveBinding(d_1 + pRef_offset, &the_groove->object_data.shear_info.y_period);
                }
                if (d_2 >= 0) {
                    AddFunkGrooveBinding(d_2 + pRef_offset, &the_groove->object_data.shear_info.z_period);
                }
            }
            GetThreeFloats(pF,
                &the_groove->object_centre.v[0],
                &the_groove->object_centre.v[1],
                &the_groove->object_centre.v[2]);
            GetThreeFloatPercents(
                pF,
                &the_groove->object_data.shear_info.x_magnitude,
                &the_groove->object_data.shear_info.y_magnitude,
                &the_groove->object_data.shear_info.z_magnitude);

            break;
        default:
            break;
        }
    }
}

// IDA: void __usercall KillGroovadelic(int pOwner@<EAX>)
void KillGroovadelic(int pOwner) {
    int i;
    tGroovidelic_spec* the_groove;
    LOG_TRACE("(%d)", pOwner);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall KillFunkotronic(int pOwner@<EAX>)
void KillFunkotronic(int pOwner) {
    int i;
    tFunkotronic_spec* the_funk;
    LOG_TRACE("(%d)", pOwner);
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __usercall DeleteBastards@<EAX>(br_actor *pActor@<EAX>, br_matrix34 *pMatrix@<EDX>, void *pArg@<EBX>)
br_uint_32 DeleteBastards(br_actor* pActor, br_matrix34* pMatrix, void* pArg) {
    int i;
    int parent_already_doomed;
    LOG_TRACE("(%p, %p, %p)", pActor, pMatrix, pArg);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DeleteAnyZeroBastards()
void DeleteAnyZeroBastards() {
    int i;
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __usercall ApplyTransToModels@<EAX>(br_actor *pActor@<EAX>, br_matrix34 *pMatrix@<EDX>, void *pArg@<EBX>)
br_uint_32 ApplyTransToModels(br_actor* pActor, br_matrix34* pMatrix, void* pArg) {
    int i;
    br_vector3 temp_point;
    LOG_TRACE("(%p, %p, %p)", pActor, pMatrix, pArg);
    NOT_IMPLEMENTED();
}

// IDA: int __usercall FindSpecVolIndex@<EAX>(br_actor *pActor@<EAX>)
int FindSpecVolIndex(br_actor* pActor) {
    int i;
    tSpecial_volume* v;
    LOG_TRACE("(%p)", pActor);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall MungeMaterial(br_matrix34 *pMat@<EAX>, br_material *pMat_1@<EDX>, br_material *pMat_2@<EBX>, int pAxis_0@<ECX>, int pAxis_1)
void MungeMaterial(br_matrix34* pMat, br_material* pMat_1, br_material* pMat_2, int pAxis_0, int pAxis_1) {
    LOG_TRACE("(%p, %p, %p, %d, %d)", pMat, pMat_1, pMat_2, pAxis_0, pAxis_1);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall SetSpecVolMatSize(br_actor *pActor@<EAX>)
void SetSpecVolMatSize(br_actor* pActor) {
    br_model* model;
    LOG_TRACE("(%p)", pActor);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall FindInverseAndWorldBox(tSpecial_volume *pSpec@<EAX>)
void FindInverseAndWorldBox(tSpecial_volume* pSpec) {
    br_bounds bnds;
    LOG_TRACE("(%p)", pSpec);

    bnds.min.v[0] = -1.0;
    bnds.min.v[1] = -1.0;
    bnds.min.v[2] = -1.0;
    bnds.max.v[0] = 1.0;
    bnds.max.v[1] = 1.0;
    bnds.max.v[2] = 1.0;
    GetNewBoundingBox(&pSpec->bounds, &bnds, &pSpec->mat);
    BrMatrix34Inverse(&pSpec->inv_mat, &pSpec->mat);
}

// IDA: void __cdecl UpdateSpecVol()
void UpdateSpecVol() {
    int index;
    tSpecial_volume* v;
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl SaveSpecialVolumes()
void SaveSpecialVolumes() {
    tPath_name the_path;
    FILE* f;
    int i;
    tSpecial_volume* v;
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl SaveAdditionalStuff()
void SaveAdditionalStuff() {
    LOG_TRACE("()");
    STUB();
}

// IDA: br_uint_32 __cdecl ProcessMaterials(br_actor *pActor, tPMFM2CB pCallback)
br_uint_32 ProcessMaterials(br_actor* pActor, tPMFM2CB pCallback) {
    LOG_TRACE("(%p, %d)", pActor, pCallback);
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __cdecl ProcessFaceMaterials2(br_actor *pActor, tPMFM2CB pCallback)
br_uint_32 ProcessFaceMaterials2(br_actor* pActor, tPMFM2CB pCallback) {
    LOG_TRACE("(%p, %d)", pActor, pCallback);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall ChangePerspToSubdivCB(br_material *pMaterial@<EAX>)
void ChangePerspToSubdivCB(br_material* pMaterial) {
    LOG_TRACE("(%p)", pMaterial);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ChangePerspToSubdiv()
void ChangePerspToSubdiv() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __usercall ChangeSubdivToPerspCB(br_material *pMaterial@<EAX>)
void ChangeSubdivToPerspCB(br_material* pMaterial) {
    LOG_TRACE("(%p)", pMaterial);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ChangeSubdivToPersp()
void ChangeSubdivToPersp() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __cdecl ProcessFaceMaterials(br_actor *pActor, tPMFMCB pCallback)
br_uint_32 ProcessFaceMaterials(br_actor* pActor, tPMFMCB pCallback) {
    LOG_TRACE("(%p, %d)", pActor, pCallback);
    NOT_IMPLEMENTED();
}

// IDA: int __usercall DRPixelmapHasZeros@<EAX>(br_pixelmap *pm@<EAX>)
int DRPixelmapHasZeros(br_pixelmap* pm) {
    int x;
    int y;
    char* row_ptr;
    char* pp;
    int i;

    if (pm->flags & BR_PMF_NO_ACCESS) {
        return 1;
    }
    row_ptr = (char*)pm->pixels + (pm->row_bytes * pm->base_y) + pm->base_x;
    for (y = 0; y < pm->height; y++) {
        pp = row_ptr;
        for (x = 0; x < pm->width; x++) {
            if (!pp)
                return 1;
            pp++;
        }
        row_ptr += pm->row_bytes;
    }
    return 0;
}

// IDA: int __usercall StorageContainsPixelmap@<EAX>(tBrender_storage *pStorage@<EAX>, br_pixelmap *pMap@<EDX>)
int StorageContainsPixelmap(tBrender_storage* pStorage, br_pixelmap* pMap) {
    int i;

    for (i = 0; i < pStorage->pixelmaps_count; i++) {
        if (pMap == pStorage->pixelmaps[i]) {
            return 1;
        }
    }
    return 0;
}

// IDA: void __usercall HideStoredOpaqueTextures(tBrender_storage *pStorage@<EAX>)
void HideStoredOpaqueTextures(tBrender_storage* pStorage) {
    int i;

    for (i = 0; i < pStorage->materials_count; i++) {
        if (pStorage->materials[i]->colour_map && StorageContainsPixelmap(pStorage, pStorage->materials[i]->colour_map)) {
            if (!DRPixelmapHasZeros(pStorage->materials[i]->colour_map)) {
                pStorage->saved_colour_maps[i] = pStorage->materials[i]->colour_map;
                pStorage->materials[i]->colour_map = NULL;
                pStorage->materials[i]->flags &= 0xFDu;
                BrMaterialUpdate(pStorage->materials[i], BR_MATU_ALL);
            }
        }
    }
}

// IDA: void __usercall RevealStoredTransparentTextures(tBrender_storage *pStorage@<EAX>)
void RevealStoredTransparentTextures(tBrender_storage* pStorage) {
    int i;

    for (i = 0; i < pStorage->materials_count; i++) {
        if (pStorage->saved_colour_maps[i]) {
            if (DRPixelmapHasZeros(pStorage->saved_colour_maps[i])) {
                pStorage->materials[i]->colour_map = pStorage->saved_colour_maps[i];
                pStorage->saved_colour_maps[i] = NULL;
                pStorage->materials[i]->flags |= 2u;
                BrMaterialUpdate(pStorage->materials[i], BR_MATU_ALL);
            }
        }
    }
}

// IDA: void __usercall HideStoredTextures(tBrender_storage *pStorage@<EAX>)
void HideStoredTextures(tBrender_storage* pStorage) {
    int i;

    for (i = 0; i < pStorage->materials_count; i++) {
        if (pStorage->materials[i]->colour_map) {
            if (StorageContainsPixelmap(pStorage, pStorage->materials[i]->colour_map)) {
                pStorage->saved_colour_maps[i] = pStorage->materials[i]->colour_map;
                pStorage->materials[i]->colour_map = NULL;
                pStorage->materials[i]->flags &= 0xFDu;
                BrMaterialUpdate(pStorage->materials[i], BR_MATU_ALL);
            }
        }
    }
}

// IDA: void __usercall RevealStoredTextures(tBrender_storage *pStorage@<EAX>)
void RevealStoredTextures(tBrender_storage* pStorage) {
    int i;

    for (i = 0; i < pStorage->materials_count; i++) {
        if (pStorage->saved_colour_maps[i]) {
            pStorage->materials[i]->colour_map = pStorage->saved_colour_maps[i];
            pStorage->saved_colour_maps[i] = NULL;
            pStorage->materials[i]->flags |= BR_MATF_PRELIT;
            BrMaterialUpdate(pStorage->materials[i], BR_MATU_ALL);
        }
    }
}

// IDA: void __usercall SetCarStorageTexturingLevel(tBrender_storage *pStorage@<EAX>, tCar_texturing_level pNew@<EDX>, tCar_texturing_level pOld@<EBX>)
void SetCarStorageTexturingLevel(tBrender_storage* pStorage, tCar_texturing_level pNew, tCar_texturing_level pOld) {
    LOG_TRACE("(%p, %d, %d)", pStorage, pNew, pOld);
    switch (pNew) {
    case eCTL_none:
        HideStoredTextures(pStorage);
        break;

    case eCTL_transparent:
        if (pOld) {
            if (pOld == 2) {
                HideStoredOpaqueTextures(pStorage);
            }
        } else {
            RevealStoredTransparentTextures(pStorage);
        }
        break;

    case eCTL_full:
        RevealStoredTextures(pStorage);
        break;

    default:
        LOG_PANIC("not handled");
    }
}

// IDA: tCar_texturing_level __cdecl GetCarTexturingLevel()
tCar_texturing_level GetCarTexturingLevel() {
    LOG_TRACE("()");
    return gCar_texturing_level;
}

// IDA: void __usercall SetCarTexturingLevel(tCar_texturing_level pLevel@<EAX>)
void SetCarTexturingLevel(tCar_texturing_level pLevel) {
    LOG_TRACE("(%d)", pLevel);

    if (pLevel != gCar_texturing_level) {
        if (gOur_car_storage_space.models_count) {
            if (pLevel == eCTL_none) {
                HideStoredTextures(&gOur_car_storage_space);
            } else if (pLevel == eCTL_transparent) {
                if (gCar_texturing_level) {
                    if (gCar_texturing_level == 2)
                        HideStoredOpaqueTextures(&gOur_car_storage_space);
                } else {
                    RevealStoredTransparentTextures(&gOur_car_storage_space);
                }
            } else if (pLevel == eCTL_full) {
                RevealStoredTextures(&gOur_car_storage_space);
            }
        }
        if (gTheir_cars_storage_space.models_count) {
            if (pLevel == eCTL_none) {
                HideStoredTextures(&gTheir_cars_storage_space);
            } else if (pLevel == eCTL_transparent) {
                if (gCar_texturing_level) {
                    if (gCar_texturing_level == 2)
                        HideStoredOpaqueTextures(&gTheir_cars_storage_space);
                } else {
                    RevealStoredTransparentTextures(&gTheir_cars_storage_space);
                }
            } else if (pLevel == eCTL_full) {
                RevealStoredTextures(&gTheir_cars_storage_space);
            }
        }
        if (gNet_cars_storage_space.models_count) {
            if (pLevel == eCTL_none) {
                HideStoredTextures(&gTheir_cars_storage_space);
            } else if (pLevel == eCTL_transparent) {
                if (gCar_texturing_level) {
                    if (gCar_texturing_level == 2)
                        HideStoredOpaqueTextures(&gTheir_cars_storage_space);
                } else {
                    RevealStoredTransparentTextures(&gTheir_cars_storage_space);
                }
            } else if (pLevel == eCTL_full) {
                RevealStoredTextures(&gTheir_cars_storage_space);
            }
        }
    }
    gCar_texturing_level = pLevel;
}

// IDA: int __usercall HasThisSuffix@<EAX>(char *pIdent@<EAX>, char *pSuffix@<EDX>)
int HasThisSuffix(char* pIdent, char* pSuffix) {
    size_t len_ident;
    size_t len_suffix;
    LOG_TRACE("(\"%s\", \"%s\")", pIdent, pSuffix);
    NOT_IMPLEMENTED();
}

// IDA: br_material* __usercall UnsuffixedMaterial@<EAX>(char *pOld_ident@<EAX>, char *pSuffix@<EDX>)
br_material* UnsuffixedMaterial(char* pOld_ident, char* pSuffix) {
    br_material* result;
    int unsuffixed_len;
    char* new_id;
    LOG_TRACE("(\"%s\", \"%s\")", pOld_ident, pSuffix);
    NOT_IMPLEMENTED();
}

// IDA: br_material* __usercall RoadUntexToPersp@<EAX>(br_model *pModel@<EAX>, tU16 pFace@<EDX>)
br_material* RoadUntexToPersp(br_model* pModel, tU16 pFace) {
    br_material* old_mat;
    br_material* new_mat;
    LOG_TRACE("(%p, %d)", pModel, pFace);
    NOT_IMPLEMENTED();
}

// IDA: br_material* __usercall WallLinearToUntex@<EAX>(br_model *pModel@<EAX>, tU16 pFace@<EDX>)
br_material* WallLinearToUntex(br_model* pModel, tU16 pFace) {
    br_material* old_mat;
    br_material* new_mat;
    LOG_TRACE("(%p, %d)", pModel, pFace);
    NOT_IMPLEMENTED();
}

// IDA: br_material* __usercall WallUntexToLinear@<EAX>(br_model *pModel@<EAX>, tU16 pFace@<EDX>)
br_material* WallUntexToLinear(br_model* pModel, tU16 pFace) {
    br_material* old_mat;
    br_material* new_mat;
    LOG_TRACE("(%p, %d)", pModel, pFace);
    NOT_IMPLEMENTED();
}

// IDA: br_material* __usercall WallUntexToPersp@<EAX>(br_model *pModel@<EAX>, tU16 pFace@<EDX>)
br_material* WallUntexToPersp(br_model* pModel, tU16 pFace) {
    br_material* old_mat;
    br_material* new_mat;
    LOG_TRACE("(%p, %d)", pModel, pFace);
    NOT_IMPLEMENTED();
}

// IDA: br_material* __usercall WallLinearToPersp@<EAX>(br_model *pModel@<EAX>, tU16 pFace@<EDX>)
br_material* WallLinearToPersp(br_model* pModel, tU16 pFace) {
    br_material* old_mat;
    br_material* new_mat;
    LOG_TRACE("(%p, %d)", pModel, pFace);
    NOT_IMPLEMENTED();
}

// IDA: tRoad_texturing_level __cdecl GetRoadTexturingLevel()
tRoad_texturing_level GetRoadTexturingLevel() {
    return gRoad_texturing_level;
}

// IDA: void __usercall SetRoadTexturingLevel(tRoad_texturing_level pLevel@<EAX>)
void SetRoadTexturingLevel(tRoad_texturing_level pLevel) {
    LOG_TRACE("(%d)", pLevel);
    gRoad_texturing_level = pLevel;
}

// IDA: void __usercall ReallySetRoadTexturingLevel(tRoad_texturing_level pLevel@<EAX>)
void ReallySetRoadTexturingLevel(tRoad_texturing_level pLevel) {
    LOG_TRACE("(%d)", pLevel);
    NOT_IMPLEMENTED();
}

// IDA: tWall_texturing_level __cdecl GetWallTexturingLevel()
tWall_texturing_level GetWallTexturingLevel() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __usercall SetWallTexturingLevel(tWall_texturing_level pLevel@<EAX>)
void SetWallTexturingLevel(tWall_texturing_level pLevel) {
    LOG_TRACE("(%d)", pLevel);
    gWall_texturing_level = pLevel;
}

// IDA: void __usercall ReallySetWallTexturingLevel(tWall_texturing_level pLevel@<EAX>)
void ReallySetWallTexturingLevel(tWall_texturing_level pLevel) {
    static tPMFMCB* tweaker[3][3];
    LOG_TRACE("(%d)", pLevel);
    NOT_IMPLEMENTED();
}

// IDA: br_material* __usercall DisposeSuffixedMaterials@<EAX>(br_model *pModel@<EAX>, tU16 pFace@<EDX>)
br_material* DisposeSuffixedMaterials(br_model* pModel, tU16 pFace) {
    size_t max_suffix_len;
    br_material* mat;
    br_material* victim;
    static char* suffixes[3];
    int s;
    char* id;
    LOG_TRACE("(%p, %d)", pModel, pFace);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DisposeTexturingMaterials()
void DisposeTexturingMaterials() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __cdecl SetAccessoryRenderingCB(br_actor *pActor, void *pFlag)
intptr_t SetAccessoryRenderingCB(br_actor* pActor, void* pFlag) {
    if (pActor->identifier && *pActor->identifier == '&') {
        pActor->render_style = *(br_uint_8*)pFlag;
    }
    return 0;
}

// IDA: void __usercall SetAccessoryRendering(int pOn@<EAX>)
void SetAccessoryRendering(int pOn) {
    int style;

    LOG_TRACE("(%d)", pOn);

    if (gTrack_actor) {
        if (pOn) {
            style = BR_RSTYLE_FACES;
        } else {
            style = BR_RSTYLE_NONE;
        }
        DRActorEnumRecurse(gTrack_actor, (br_actor_enum_cbfn*)SetAccessoryRenderingCB, &style);
    }
    gRendering_accessories = pOn;
}

// IDA: int __cdecl GetAccessoryRendering()
int GetAccessoryRendering() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __usercall SetCarSimplificationLevel(int pLevel@<EAX>)
void SetCarSimplificationLevel(int pLevel) {
    LOG_TRACE("(%d)", pLevel);
    gCar_simplification_level = pLevel;
}

// IDA: int __cdecl GetCarSimplificationLevel()
int GetCarSimplificationLevel() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __usercall ParseSpecialVolume(FILE *pF@<EAX>, tSpecial_volume *pSpec@<EDX>, char *pScreen_name_str@<EBX>)
void ParseSpecialVolume(FILE* pF, tSpecial_volume* pSpec, char* pScreen_name_str) {
    char s[256];
    pSpec->gravity_multiplier = GetAScalar(pF);
    pSpec->viscosity_multiplier = GetAScalar(pF);
    pSpec->car_damage_per_ms = GetAScalar(pF);
    pSpec->ped_damage_per_ms = GetAScalar(pF);
    pSpec->camera_special_effect_index = GetAnInt(pF);
    pSpec->sky_col = GetAnInt(pF);

    GetAString(pF, s);
    if (pScreen_name_str) {
        strcpy(pScreen_name_str, s);
    } else {
        pSpec->screen_material = BrMaterialFind(s);
    }
    pSpec->entry_noise = GetAnInt(pF);
    pSpec->exit_noise = GetAnInt(pF);
    pSpec->engine_noise_index = GetAnInt(pF);
    pSpec->material_modifier_index = GetAnInt(pF);
}

// IDA: void __usercall AddExceptionToList(tException_list *pDst@<EAX>, tException_list pNew@<EDX>)
void AddExceptionToList(tException_list* pDst, tException_list pNew) {
    LOG_TRACE("(%p, %d)", pDst, pNew);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall LoadExceptionsFile(char *pName@<EAX>)
void LoadExceptionsFile(char* pName) {
    FILE* f;
    char line[256];
    char* tok;
    int file_version;
    tException_list e;
    char delimiters[4];
    LOG_TRACE("(\"%s\")", pName);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall LoadExceptionsFileForTrack(char *pTrack_file_name@<EAX>)
void LoadExceptionsFileForTrack(char* pTrack_file_name) {
    tPath_name exceptions_file_name;
    LOG_TRACE("(\"%s\")", pTrack_file_name);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl FreeExceptions()
void FreeExceptions() {
    tException_list list;
    tException_list next;
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __usercall LoadTrack(char *pFile_name@<EAX>, tTrack_spec *pTrack_spec@<EDX>, tRace_info *pRace_info@<EBX>)
void LoadTrack(char* pFile_name, tTrack_spec* pTrack_spec, tRace_info* pRace_info) {
    char temp_name[14];
    FILE* f;
    FILE* non_car_f;
    FILE* g;
    int i;
    int j;
    int k;
    int group;
    int needs_updating;
    int killed_sky;
    int line_count;
    int ped_subs_index;
    int sl;
    int num_materials;
    int count;
    int num_non_cars;
    int style;
    int cp_rect_w[2];
    int cp_rect_h[2];
    tPath_name the_path;
    tPath_name general_file_path;
    char s[256];
    char* str;
    float temp_float;
    br_actor* new_root;
    br_vector3 a;
    br_vector3 b;
    tU16 sky_pixels_high;
    tNon_car_spec* non_car;
    tSpecial_volume* spec;
    br_vector3 p[3];
    br_vector3 v1;
    br_vector3 v2;
    br_vector3 temp_v;
    br_bounds temp_bounds;
    tPed_subs* ped_subs;
    br_pixelmap* sky;
    br_material* material;
    LOG_TRACE("(\"%s\", %p, %p)", pFile_name, pTrack_spec, pRace_info);

    killed_sky = 0;
    PathCat(the_path, gApplication_path, "RACES");
    PathCat(the_path, the_path, pFile_name);
    f = DRfopen(the_path, "rt");
    if (!f) {
        FatalError(50);
    }
    GetALineAndDontArgue(f, s);
    str = strtok(s, "\t ,/");
    if (strcmp(str, "VERSION") == 0) {
        str = strtok(NULL, "\t ,/");
        sscanf(str, "%d", &gRace_file_version);
        GetALineAndDontArgue(f, s);
        str = strtok(s, "\t ,/");
    } else {
        gRace_file_version = 0;
    }
    sscanf(str, "%f", &temp_float);
    pRace_info->initial_position.v[0] = temp_float;
    str = strtok(0, "\t ,/");
    sscanf(str, "%f", &temp_float);
    pRace_info->initial_position.v[1] = temp_float;
    str = strtok(0, "\t ,/");
    sscanf(str, "%f", &temp_float);
    pRace_info->initial_position.v[2] = temp_float;
    PossibleService();
    GetALineAndDontArgue(f, s);
    str = strtok(s, "\t ,/");
    sscanf(str, "%f", &temp_float);
    pRace_info->initial_yaw = temp_float;
    GetThreeInts(f, pRace_info->initial_timer, &pRace_info->initial_timer[1], &pRace_info->initial_timer[2]);
    pRace_info->total_laps = GetAnInt(f);
    GetThreeInts(f, &pRace_info->bonus_score[0][0], &pRace_info->bonus_score[0][1], &pRace_info->bonus_score[0][2]);
    GetThreeInts(f, &pRace_info->bonus_score[1][0], &pRace_info->bonus_score[1][1], &pRace_info->bonus_score[1][2]);
    GetThreeInts(f, &pRace_info->bonus_score[2][0], &pRace_info->bonus_score[2][1], &pRace_info->bonus_score[2][2]);
    if (gRace_file_version > 1) {
        GetPairOfInts(f, &cp_rect_w[0], &cp_rect_w[1]);
        GetPairOfInts(f, &cp_rect_h[0], &cp_rect_h[1]);
    }
    PossibleService();
    pRace_info->check_point_count = GetAnInt(f);
    for (i = 0; i < pRace_info->check_point_count; i++) {
        PossibleService();
        if (gProgram_state.sausage_eater_mode) {
            GetALineAndDontArgue(f, s);
            GetThreeInts(
                f,
                &pRace_info->checkpoints[i].time_value[0],
                &pRace_info->checkpoints[i].time_value[1],
                &pRace_info->checkpoints[i].time_value[2]);
        } else {
            GetThreeInts(
                f,
                &pRace_info->checkpoints[i].time_value[0],
                &pRace_info->checkpoints[i].time_value[1],
                &pRace_info->checkpoints[i].time_value[2]);
            GetALineAndDontArgue(f, s);
        }
        pRace_info->checkpoints[i].quad_count = GetAnInt(f);
        for (j = 0; j < pRace_info->checkpoints[i].quad_count; j++) {
            for (k = 0; k < 4; ++k) {
                GetThreeScalars(
                    f,
                    &pRace_info->checkpoints[i].vertices[j][k].v[0],
                    &pRace_info->checkpoints[i].vertices[j][k].v[1],
                    &pRace_info->checkpoints[i].vertices[j][k].v[2]);
            }
            p[0] = pRace_info->checkpoints[i].vertices[j][0];
            p[1] = pRace_info->checkpoints[i].vertices[j][1];
            p[2] = pRace_info->checkpoints[i].vertices[j][2];
            pRace_info->checkpoints[i].normal[j].v[0] = (p[2].v[2] - p[0].v[2]) * (p[1].v[1] - p[0].v[1]) - (p[1].v[2] - p[0].v[2]) * (p[2].v[1] - p[0].v[1]);
            pRace_info->checkpoints[i].normal[j].v[1] = (p[1].v[2] - p[0].v[2]) * (p[2].v[0] - p[0].v[0]) - (p[2].v[2] - p[0].v[2]) * (p[1].v[0] - p[0].v[0]);
            pRace_info->checkpoints[i].normal[j].v[2] = (p[2].v[1] - p[0].v[1]) * (p[1].v[0] - p[0].v[0]) - (p[1].v[1] - p[0].v[1]) * (p[2].v[0] - p[0].v[0]);
        }
        if (gRace_file_version > 0) {
            if (gRace_file_version == 1) {
                GetPairOfInts(f, pRace_info->checkpoints[i].map_left, &pRace_info->checkpoints[i].map_left[1]);
                GetPairOfInts(f, pRace_info->checkpoints[i].map_top, &pRace_info->checkpoints[i].map_top[1]);
                GetPairOfInts(f, pRace_info->checkpoints[i].map_right, &pRace_info->checkpoints[i].map_right[1]);
                GetPairOfInts(f, pRace_info->checkpoints[i].map_bottom, &pRace_info->checkpoints[i].map_bottom[1]);
            } else {
                GetPairOfInts(f, pRace_info->checkpoints[i].map_left, &pRace_info->checkpoints[i].map_left[1]);
                GetPairOfInts(f, pRace_info->checkpoints[i].map_top, &pRace_info->checkpoints[i].map_top[1]);
                pRace_info->checkpoints[i].map_right[0] = cp_rect_w[0] + pRace_info->checkpoints[i].map_left[0];
                pRace_info->checkpoints[i].map_right[1] = cp_rect_w[0] + pRace_info->checkpoints[i].map_left[1];
                pRace_info->checkpoints[i].map_bottom[0] = cp_rect_h[0] + pRace_info->checkpoints[i].map_top[0];
                pRace_info->checkpoints[i].map_bottom[1] = cp_rect_h[0] + pRace_info->checkpoints[i].map_top[1];
            }
        }
    }
    if (gRace_file_version <= 3) {
        LoadSomePixelmaps(&gTrack_storage_space, f);
    } else if (gAusterity_mode) {
        SkipNLines(f);
        LoadSomePixelmaps(&gTrack_storage_space, f);
    } else {
        LoadSomePixelmaps(&gTrack_storage_space, f);
        SkipNLines(f);
    }
    LoadSomeShadeTables(&gTrack_storage_space, f);
    if (gRace_file_version <= 3) {
        LoadSomeMaterials(&gTrack_storage_space, f);
    } else if (gAusterity_mode) {
        SkipNLines(f);
        LoadSomeMaterials(&gTrack_storage_space, f);
    } else {
        LoadSomeMaterials(&gTrack_storage_space, f);
        SkipNLines(f);
    }
    for (i = 0; gTrack_storage_space.materials_count > i; ++i) {
        PossibleService();
        if (gTrack_storage_space.materials[i]->flags & 7) {
            gTrack_storage_space.materials[i]->flags &= 0xFFFFFFF8;
            if (gTrack_storage_space.materials[i]->flags & 0x1000) {
                gTrack_storage_space.materials[i]->colour_map_1 = (br_pixelmap*)12345;
                gTrack_storage_space.materials[i]->flags &= 0xFFFFEFFF;
            }
            BrMaterialUpdate(gTrack_storage_space.materials[i], BR_MATU_RENDERING);
        }
    }
    if (gRace_file_version <= 5) {
        LoadSomeTrackModels(&gTrack_storage_space, f);
    } else if (gAusterity_mode) {
        SkipNLines(f);
        LoadSomeTrackModels(&gTrack_storage_space, f);
    } else {
        LoadSomeTrackModels(&gTrack_storage_space, f);
        SkipNLines(f);
    }

    PrintMemoryDump(0, "JUST LOADED IN TEXTURES/MATS/MODELS FOR TRACK");
    if (gRace_file_version <= 5) {
        GetALineAndDontArgue(f, s);
        str = strtok(s, "\t ,/");
        PathCat(the_path, gApplication_path, "ACTORS");
        PathCat(the_path, the_path, str);
    } else if (gAusterity_mode) {
        GetALineAndDontArgue(f, s);
        GetALineAndDontArgue(f, s);
        str = strtok(s, "\t ,/");
        PathCat(the_path, gApplication_path, "ACTORS");
        PathCat(the_path, the_path, str);
    } else {
        GetALineAndDontArgue(f, s);
        str = strtok(s, "\t ,/");
        PathCat(the_path, gApplication_path, "ACTORS");
        PathCat(the_path, the_path, str);
        GetALineAndDontArgue(f, s);
    }
    pTrack_spec->the_actor = BrActorLoad(the_path);
    if (gRace_file_version <= 6) {
        gDefault_blend_pc = 75;
    } else {
        GetAString(f, s);
        if (!sscanf(s, "%d", &gDefault_blend_pc) || gDefault_blend_pc < 0 || gDefault_blend_pc > 100) {
            PDFatalError("Wanted a default blend percentage. Didn't get one. Old data file?");
        }
    }
    PossibleService();
    ExtractColumns(pTrack_spec);
    for (i = 0; gTrack_storage_space.models_count > i; ++i) {
        PossibleService();
        if (gTrack_storage_space.models[i] && gTrack_storage_space.models[i]->flags & 0x82) {
            gTrack_storage_space.models[i]->flags &= 0xFF7Du;
            for (group = 0; group < V11MODEL(gTrack_storage_space.models[i])->ngroups; group++) {
                int f = V11MODEL(gTrack_storage_space.models[i])->groups[group].face_user[0];
                material = gTrack_storage_space.models[i]->faces[f].material;
                V11MODEL(gTrack_storage_space.models[i])->groups[group].face_colours_material = material;
                if (material && !material->index_shade) {
                    material->index_shade = BrTableFind("DRRENDER.TAB");
                    BrMaterialUpdate(material, 0x7FFFu);
                }
            }
            DodgyModelUpdate(gTrack_storage_space.models[i]);
        }
    }
    PrintMemoryDump(0, "JUST LOADED IN TRACK ACTOR AND PROCESSED COLUMNS");
    gTrack_actor = pTrack_spec->the_actor;
    if (!gRendering_accessories && !gNet_mode) {
        PossibleService();
        style = BR_RSTYLE_NONE;
        DRActorEnumRecurse(gTrack_actor, (br_actor_enum_cbfn*)SetAccessoryRenderingCB, &style);
    }
    BrActorAdd(gUniverse_actor, pTrack_spec->the_actor);
    GetALineAndDontArgue(f, s);
    str = strtok(s, "\t ,/");
    str = strtok(str, ".");
    strcat(str, ".DAT");
    PathCat(gAdditional_model_path, gApplication_path, "MODELS");
    PathCat(gAdditional_model_path, gAdditional_model_path, str);
    gNumber_of_additional_models = 0;
    PossibleService();
    str = strtok(s, "\t ,/");
    str = strtok(str, ".");
    strcat(str, ".ACT");
    PathCat(gAdditional_actor_path, gApplication_path, "ACTORS");
    PathCat(gAdditional_actor_path, gAdditional_actor_path, str);
    gAdditional_actors = BrActorAllocate(0, 0);
    BrActorAdd(gUniverse_actor, gAdditional_actors);
    gLast_actor = 0;
    SaveAdditionalStuff();
    GetAString(f, s);
    sky = BrMapFind(s);
    if (gAusterity_mode && sky) {
        for (i = 0; gTrack_storage_space.pixelmaps_count > i; ++i) {
            if (gTrack_storage_space.pixelmaps[i] == sky) {
                BrMapRemove(gTrack_storage_space.pixelmaps[i]);
                BrPixelmapFree(gTrack_storage_space.pixelmaps[i]);
                gTrack_storage_space.pixelmaps[i] = gTrack_storage_space.pixelmaps[gTrack_storage_space.pixelmaps_count - 1];
                gTrack_storage_space.pixelmaps_count--;
                break;
            }
        }
        sky = 0;
        killed_sky = 1;
    }
    gProgram_state.default_depth_effect.sky_texture = sky;
    if (sky) {
        sky_pixels_high = gProgram_state.default_depth_effect.sky_texture->height;
    } else {
        sky_pixels_high = 100;
    }
    PossibleService();

    gSky_image_width = (360.0 / (double)BR_ANGLE_DEG(GetAnInt(f)));
    gSky_image_height = BR_ANGLE_DEG(GetAScalar(f));

    gSky_image_underground = (uint16_t)gSky_image_height * (sky_pixels_high - GetAnInt(f)) / sky_pixels_high;

    MungeRearviewSky();
    PossibleService();
    gProgram_state.default_depth_effect.type = GetALineAndInterpretCommand(f, gDepth_effect_names, 2);
    GetPairOfInts(f, &gProgram_state.default_depth_effect.start, &gProgram_state.default_depth_effect.end);
    if (killed_sky && gProgram_state.default_depth_effect.type != eDepth_effect_fog) {
        gProgram_state.default_depth_effect.type = eDepth_effect_fog;
        gProgram_state.default_depth_effect.start = 7;
        gProgram_state.default_depth_effect.end = 0;
    }
    InstantDepthChange(
        gProgram_state.default_depth_effect.type,
        gProgram_state.default_depth_effect.sky_texture,
        gProgram_state.default_depth_effect.start,
        gProgram_state.default_depth_effect.end);
    gSwap_sky_texture = 0;
    if (!GetSkyTextureOn()) {
        ToggleSkyQuietly();
    }
    gSwap_depth_effect_type = -1;
    if (!GetDepthCueingOn()) {
        ToggleDepthCueingQuietly();
    }
    PossibleService();
    gDefault_engine_noise_index = GetAnInt(f);
    gDefault_water_spec_vol = &gDefault_default_water_spec_vol;
    gProgram_state.special_volume_count = GetAnInt(f);
    if (gProgram_state.special_volume_count) {
        gProgram_state.special_volumes = BrMemAllocate(sizeof(tSpecial_volume) * gProgram_state.special_volume_count, kMem_special_volume);
        i = 0;
        spec = gProgram_state.special_volumes;
        for (i = 0; i < gProgram_state.special_volume_count; i++) {
            PossibleService();
            spec->no_mat = 0;
            GetALineAndDontArgue(f, s);
            if (strcmp(s, "NEW IMPROVED!") == 0) {
                GetThreeScalars(f, &spec->mat.m[0][0], &spec->mat.m[0][1], &spec->mat.m[0][2]);
                GetThreeScalars(f, &spec->mat.m[1][0], &spec->mat.m[1][1], &spec->mat.m[1][2]);
                GetThreeScalars(f, &spec->mat.m[2][0], &spec->mat.m[2][1], &spec->mat.m[2][2]);
                GetThreeScalars(f, &spec->mat.m[3][0], &spec->mat.m[3][1], &spec->mat.m[3][2]);
                FindInverseAndWorldBox(spec);
                ParseSpecialVolume(f, spec, 0);
            } else if (strcmp(s, "DEFAULT WATER") == 0) {
                spec->bounds.min.v[0] = 0.0;
                spec->bounds.min.v[1] = 0.0;
                spec->bounds.min.v[2] = 0.0;
                spec->bounds.max.v[0] = 0.0;
                spec->bounds.max.v[1] = 0.0;
                spec->bounds.max.v[2] = 0.0;
                ParseSpecialVolume(f, spec, 0);
                gDefault_water_spec_vol = spec;
                spec->no_mat = 1;
            } else {
                TELL_ME_IF_WE_PASS_THIS_WAY();
                spec->no_mat = 0;
                str = strtok(s, "\t ,/");
                sscanf(str, "%f", &spec->bounds.min.v[0]);
                str = strtok(0, "\t ,/");
                sscanf(str, "%f", &spec->bounds.min.v[1]);
                str = strtok(0, "\t ,/");
                sscanf(str, "%f", &spec->bounds.min.v[2]);
                GetThreeScalars(f, &spec->bounds.max.v[0], &spec->bounds.max.v[1], &spec->bounds.max.v[2]);
                BrMatrix34Identity(&spec->mat);
                for (k = 0; k < 3; ++k) {
                    // TODO: ? spec->mat.m[3][k] = (v64[k] + v63[k]) / 2.0;
                    // spec->mat.m[0][4 * k] = v64[k] - spec->mat.m[3][k];
                }
                ParseSpecialVolume(f, spec, 0);
            }
            spec++;
        }
    }
    GetAString(f, s);
    gProgram_state.standard_screen = BrMaterialFind(s);
    GetAString(f, s);
    gProgram_state.standard_screen_dark = BrMaterialFind(s);
    GetAString(f, s);
    gProgram_state.standard_screen_fog = BrMaterialFind(s);
    gProgram_state.special_screens_count = GetAnInt(f);
    if (gProgram_state.special_screens_count) {
        gProgram_state.special_screens = BrMemAllocate(sizeof(tSpecial_screen) * gProgram_state.special_screens_count, kMem_special_screen);
        for (i = 0; i < gProgram_state.special_screens_count; i++) {
            GetFourScalars(
                f,
                &gProgram_state.special_screens[i].min_x,
                &gProgram_state.special_screens[i].min_z,
                &gProgram_state.special_screens[i].max_x,
                &gProgram_state.special_screens[i].max_z);
            GetAString(f, s);
            material = BrMaterialFind(s);
            gProgram_state.special_screens[i].material = material;
        }
    }
    PossibleService();
    GetAString(f, s);
    pRace_info->map_image = LoadPixelmap(s);
    PrintMemoryDump(0, "JUST LOADING SKY/SPEC VOLS/SCREENS/MAP");
    for (i = 0; i < 4; ++i) {
        GetThreeScalars(
            f,
            &pRace_info->map_transformation.m[i][0],
            &pRace_info->map_transformation.m[i][1],
            &pRace_info->map_transformation.m[i][2]);
    }
    GetALineAndDontArgue(f, s);
    AddFunkotronics(f, -2, 720);
    GetALineAndDontArgue(f, s);
    AddGroovidelics(f, -2, gUniverse_actor, 720, 0);
    PossibleService();
    PrintMemoryDump(0, "JUST LOADING IN FUNKS AND GROOVES");
    ped_subs = 0;
    count = 0;
    if (gRace_file_version > 3) {
        line_count = GetAnInt(f);
        if (gAusterity_mode) {
            if (line_count >= 0) {
                PathCat(the_path, gApplication_path, "PEDSUBS.TXT");
                g = DRfopen(the_path, "rt");
                if (!g) {
                    FatalError(50);
                }
                for (i = 0; i < line_count; ++i) {
                    SkipNLines(g);
                }
                count = GetAnInt(g);
                ped_subs = BrMemAllocate(sizeof(tPed_subs) * count, kMem_misc);
                for (ped_subs_index = 0; ped_subs_index < count; ped_subs_index++) {
                    GetPairOfInts(g, &ped_subs[i].orig, &ped_subs[i].subs);
                }
                fclose(g);
            }
        }
    }
    PossibleService();
    LoadInPedestrians(f, count, ped_subs);
    if (ped_subs) {
        BrMemFree(ped_subs);
    }
    PrintMemoryDump(0, "JUST LOADED IN PEDS");
    LoadInOppoPaths(f);
    PrintMemoryDump(0, "JUST LOADED IN OPPO PATHS");
    num_materials = GetAnInt(f);
    for (i = 0; i < num_materials; i++) {
        PossibleService();
        pRace_info->material_modifiers[i].car_wall_friction = GetAScalar(f);
        pRace_info->material_modifiers[i].tyre_road_friction = GetAScalar(f);
        pRace_info->material_modifiers[i].down_force = GetAScalar(f);
        pRace_info->material_modifiers[i].bumpiness = GetAScalar(f);
        pRace_info->material_modifiers[i].tyre_noise_index = GetAnInt(f);
        pRace_info->material_modifiers[i].crash_noise_index = GetAnInt(f);
        pRace_info->material_modifiers[i].scrape_noise_index = GetAnInt(f);
        pRace_info->material_modifiers[i].sparkiness = GetAScalar(f);
        pRace_info->material_modifiers[i].smoke_type = GetAnInt(f);
        GetAString(f, s);
        str = strtok(s, ".");

        if (!strcmp(s, "none") || !strcmp(s, "NONE") || !strcmp(s, "0") || !strcmp(s, "1")) {
            pRace_info->material_modifiers[i].skid_mark_material = NULL;
        } else {
            sl = strlen(str);
            strcat(str, ".PIX");
            LoadSinglePixelmap(&gTrack_storage_space, str);
            str[sl] = 0;
            strcat(str, ".MAT");
            material = LoadSingleMaterial(&gTrack_storage_space, str);
            pRace_info->material_modifiers[i].skid_mark_material = material;
        }
    }
    for (i = num_materials; i < 10; ++i) {
        pRace_info->material_modifiers[i].car_wall_friction = 1.0;
        pRace_info->material_modifiers[i].tyre_road_friction = 1.0;
        pRace_info->material_modifiers[i].down_force = 1.0;
        pRace_info->material_modifiers[i].bumpiness = 0.0;
        pRace_info->material_modifiers[i].tyre_noise_index = 0;
        pRace_info->material_modifiers[i].crash_noise_index = 0;
        pRace_info->material_modifiers[i].scrape_noise_index = 0;
        pRace_info->material_modifiers[i].sparkiness = 1.0;
        pRace_info->material_modifiers[i].smoke_type = 1;
        pRace_info->material_modifiers[i].skid_mark_material = 0;
    }
    i = 10;
    pRace_info->material_modifiers[i].car_wall_friction = 1.0;
    pRace_info->material_modifiers[i].tyre_road_friction = 1.0;
    pRace_info->material_modifiers[i].down_force = 0.0;
    pRace_info->material_modifiers[i].bumpiness = 0.0;
    pRace_info->material_modifiers[i].tyre_noise_index = -1;
    pRace_info->material_modifiers[i].crash_noise_index = 0;
    pRace_info->material_modifiers[i].scrape_noise_index = 0;
    pRace_info->material_modifiers[i].sparkiness = 0.0;
    pRace_info->material_modifiers[i].smoke_type = 1;
    pRace_info->material_modifiers[i].skid_mark_material = 0;
    gDefault_water_spec_vol->material_modifier_index = i;
    num_non_cars = GetAnInt(f);
    non_car = BrMemCalloc(num_non_cars + 5, sizeof(tNon_car_spec), kMem_non_car_spec);
    if (!non_car && num_non_cars) {
        FatalError(50);
    }
    gProgram_state.non_cars = non_car;
    gProgram_state.num_non_car_spaces = num_non_cars + NONCAR_UNUSED_SLOTS;
    for (i = 0; i < COUNT_OF(gNon_car_spec_list); ++i) {
        gNon_car_spec_list[i] = 0;
    }
    for (i = 0; i < NONCAR_UNUSED_SLOTS; i++) {
        non_car->collision_info.driver = eDriver_non_car_unused_slot;
        non_car++;
    }
    i = 0;
    for (i = 0; i < num_non_cars; i++) {
        PossibleService();
        GetAString(f, s);
        PathCat(the_path, gApplication_path, "NONCARS");
        PathCat(the_path, the_path, s);
        non_car_f = DRfopen(the_path, "rt");
        if (!non_car_f) {
            FatalError(107, the_path);
        }
        ReadNonCarMechanicsData(non_car_f, non_car);
        PossibleService();
        ReadShrapnelMaterials(non_car_f, &non_car->collision_info);
        gNon_car_spec_list[non_car->collision_info.index] = i + 1;
        fclose(non_car_f);
        non_car++;
    }
    GetSmokeShadeTables(f);
    pRace_info->number_of_net_start_points = GetAnInt(f);
    for (i = 0; i < pRace_info->number_of_net_start_points; i++) {
        GetThreeScalars(
            f,
            &pRace_info->net_starts[i].pos.v[0],
            &pRace_info->net_starts[i].pos.v[1],
            &pRace_info->net_starts[i].pos.v[2]);
        pRace_info->net_starts[i].yaw = GetAScalar(f);
    }
    if (gRace_file_version <= 2) {
        LoadInKevStuff(NULL);
    } else {
        LoadInKevStuff(f);
    }
    if (gRace_file_version < 5) {
        gYon_multiplier = 1.0;
    } else {
        gYon_multiplier = GetAScalar(f);
    }
    GetAString(f, s);
    if (strcmp(s, pFile_name) != 0) {
        FatalError(115, pFile_name);
    }
    fclose(f);
}

// IDA: br_uint_32 __cdecl RemoveBounds(br_actor *pActor, void *pArg)
br_uint_32 RemoveBounds(br_actor* pActor, void* pArg) {
    LOG_TRACE("(%p, %p)", pActor, pArg);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall RemoveBoundsStructures(br_actor *pActor@<EAX>)
void RemoveBoundsStructures(br_actor* pActor) {
    LOG_TRACE("(%p)", pActor);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall FreeTrack(tTrack_spec *pTrack_spec@<EAX>)
void FreeTrack(tTrack_spec* pTrack_spec) {
    int i;
    tNon_car_spec* non_car;
    LOG_TRACE("(%p)", pTrack_spec);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall ProcessTrack(br_actor *pWorld@<EAX>, tTrack_spec *pTrack_spec@<EDX>, br_actor *pCamera@<EBX>, br_matrix34 *pCamera_to_world_transform@<ECX>, int pRender_blends)
void ProcessTrack(br_actor* pWorld, tTrack_spec* pTrack_spec, br_actor* pCamera, br_matrix34* pCamera_to_world_transform, int pRender_blends) {
    LOG_TRACE("(%p, %p, %p, %p, %d)", pWorld, pTrack_spec, pCamera, pCamera_to_world_transform, pRender_blends);

    RenderTrack(pWorld, pTrack_spec, pCamera, pCamera_to_world_transform, pRender_blends);
    if (gAdditional_actors) {
        if (!pRender_blends) {
            BrZbSceneRenderAdd(gAdditional_actors);
        }
    }
}

// IDA: br_scalar __cdecl NormaliseDegreeAngle(br_scalar pAngle)
br_scalar NormaliseDegreeAngle(br_scalar pAngle) {
    LOG_TRACE("(%f)", pAngle);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl FunkThoseTronics()
void FunkThoseTronics() {
    int i;
    int j;
    int iteration_count;
    int finished;
    tFunkotronic_spec* the_funk;
    br_vector3* the_proximity;
    tS32 the_time;
    tS32 old_last_time;
    tS32 time_diff;
    br_matrix23* mat_matrix;
    br_material* the_material;
    float f_the_time;
    float rot_amount;
    float f_time_diff;
    br_pixelmap* old_colour_map;
    LOG_TRACE("()");
    SILENT_STUB();
}

// IDA: void __usercall LollipopizeActor(br_actor *pSubject_actor@<EAX>, br_matrix34 *ref_to_world@<EDX>, tLollipop_mode pWhich_axis@<EBX>)
void LollipopizeActor(br_actor* pSubject_actor, br_matrix34* ref_to_world, tLollipop_mode pWhich_axis) {
    br_vector3 ref_to_subject;
    br_vector3 fixed_axis;
    br_vector3 vector_a;
    br_vector3 vector_b;
    br_matrix34 subject_to_world;
    br_matrix34 mat;
    LOG_TRACE("(%p, %p, %d)", pSubject_actor, ref_to_world, pWhich_axis);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall CalcActorGlobalPos(br_vector3 *pResult@<EAX>, br_actor *pActor@<EDX>)
void CalcActorGlobalPos(br_vector3* pResult, br_actor* pActor) {
    LOG_TRACE("(%p, %p)", pResult, pActor);
    NOT_IMPLEMENTED();
}

// IDA: int __usercall PointOutOfSight@<EAX>(br_vector3 *pPoint@<EAX>, br_scalar pMax_distance)
int PointOutOfSight(br_vector3* pPoint, br_scalar pMax_distance) {
    br_vector3 distance_vector;
    LOG_TRACE("(%p, %f)", pPoint, pMax_distance);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall PathGrooveBastard(tGroovidelic_spec *pGroove@<EAX>, tU32 pTime@<EDX>, br_matrix34 *pMat@<EBX>, int pInterrupt_it@<ECX>)
void PathGrooveBastard(tGroovidelic_spec* pGroove, tU32 pTime, br_matrix34* pMat, int pInterrupt_it) {
    br_scalar pos;
    LOG_TRACE("(%p, %d, %p, %d)", pGroove, pTime, pMat, pInterrupt_it);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall ObjectGrooveBastard(tGroovidelic_spec *pGroove@<EAX>, tU32 pTime@<EDX>, br_matrix34 *pMat@<EBX>, int pInterrupt_it@<ECX>)
void ObjectGrooveBastard(tGroovidelic_spec* pGroove, tU32 pTime, br_matrix34* pMat, int pInterrupt_it) {
    int rock_it;
    br_scalar x_size;
    br_scalar y_size;
    br_scalar z_size;
    br_scalar pos;
    br_bounds* bounds;
    LOG_TRACE("(%p, %d, %p, %d)", pGroove, pTime, pMat, pInterrupt_it);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall GrooveThisDelic(tGroovidelic_spec *pGroove@<EAX>, tU32 pTime@<EDX>, int pInterrupt_it@<EBX>)
void GrooveThisDelic(tGroovidelic_spec* pGroove, tU32 pTime, int pInterrupt_it) {
    br_actor* the_actor;
    br_vector3 actor_pos;
    br_matrix34* the_mat;
    tInterrupt_status old_path_interrupt;
    tInterrupt_status old_object_interrupt;
    LOG_TRACE("(%p, %d, %d)", pGroove, pTime, pInterrupt_it);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl GrooveThoseDelics()
void GrooveThoseDelics() {
    int i;
    tGroovidelic_spec* the_groove;
    float f_the_time;
    LOG_TRACE("()");
    SILENT_STUB();
}

// IDA: void __usercall StopGroovidelic(br_actor *pActor@<EAX>)
void StopGroovidelic(br_actor* pActor) {
    int i;
    tGroovidelic_spec* the_groove;
    LOG_TRACE("(%p)", pActor);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall SetGrooveInterrupt(int pGroove_index@<EAX>, br_matrix34 *pMatrix@<EDX>, int pPath_interrupt@<EBX>, int pObject_interrupt@<ECX>, float pPath_resumption, float pObject_resumption)
void SetGrooveInterrupt(int pGroove_index, br_matrix34* pMatrix, int pPath_interrupt, int pObject_interrupt, float pPath_resumption, float pObject_resumption) {
    tGroovidelic_spec* the_groove;
    LOG_TRACE("(%d, %p, %d, %d, %f, %f)", pGroove_index, pMatrix, pPath_interrupt, pObject_interrupt, pPath_resumption, pObject_resumption);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ResetGrooveFlags()
void ResetGrooveFlags() {
    int i;
    tGroovidelic_spec* the_groove;
    LOG_TRACE("()");

    the_groove = gGroovidelics_array;
    for (i = 0; i < gGroovidelics_array_size; i++) {
        the_groove->done_this_frame = 0;
        the_groove++;
    }
}

// IDA: tSpecial_volume* __cdecl GetDefaultSpecialVolumeForWater()
tSpecial_volume* GetDefaultSpecialVolumeForWater() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: tSpecial_volume* __usercall FindSpecialVolume@<EAX>(br_vector3 *pP@<EAX>, tSpecial_volume *pLast_vol@<EDX>)
tSpecial_volume* FindSpecialVolume(br_vector3* pP, tSpecial_volume* pLast_vol) {
    int i;
    tSpecial_volume* v;
    br_vector3 p;
    LOG_TRACE("(%p, %p)", pP, pLast_vol);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl SaveAdditionalActors()
void SaveAdditionalActors() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: br_scalar __usercall DistanceFromFace@<ST0>(br_vector3 *pPos@<EAX>, tFace_ref *pFace@<EDX>)
br_scalar DistanceFromFace(br_vector3* pPos, tFace_ref* pFace) {
    br_vector3 normal;
    LOG_TRACE("(%p, %p)", pPos, pFace);
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __cdecl CalcHighestID(br_actor *pActor, int *pHighest)
br_uint_32 CalcHighestID(br_actor* pActor, int* pHighest) {
    char s[256];
    int number;
    LOG_TRACE("(%p, %p)", pActor, pHighest);
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __cdecl SetID(br_actor *pActor, void *pArg)
br_uint_32 SetID(br_actor* pActor, void* pArg) {
    char s[256];
    LOG_TRACE("(%p, %p)", pActor, pArg);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall UniquificateActorsName(br_actor *pUniverse_actor@<EAX>, br_actor *pActor@<EDX>)
void UniquificateActorsName(br_actor* pUniverse_actor, br_actor* pActor) {
    int highest;
    LOG_TRACE("(%p, %p)", pUniverse_actor, pActor);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall AccessoryHeadup(br_actor *pActor@<EAX>, char *pPrefix@<EDX>)
void AccessoryHeadup(br_actor* pActor, char* pPrefix) {
    char s[256];
    int i;
    br_actor* original_actor;
    LOG_TRACE("(%p, \"%s\")", pActor, pPrefix);
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __cdecl CalcHighestNonAmID(br_actor *pActor, int *pHighest)
br_uint_32 CalcHighestNonAmID(br_actor* pActor, int* pHighest) {
    char s[256];
    int number;
    LOG_TRACE("(%p, %p)", pActor, pHighest);
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __cdecl SetIDAndDupModel(br_actor *pActor, void *pArg)
br_uint_32 SetIDAndDupModel(br_actor* pActor, void* pArg) {
    char s[256];
    char s2[256];
    br_model* new_model;
    LOG_TRACE("(%p, %p)", pActor, pArg);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall DuplicateIfNotAmpersand(br_actor *pActor@<EAX>)
void DuplicateIfNotAmpersand(br_actor* pActor) {
    int highest;
    LOG_TRACE("(%p)", pActor);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall DropActor(int pIndex@<EAX>)
void DropActor(int pIndex) {
    FILE* f;
    tPath_name the_path;
    char s[256];
    int i;
    int face_bastard;
    int face_count;
    tBounds kev_bounds;
    tFace_ref the_list[50];
    br_scalar nearest_bastard;
    br_scalar distance_bastard;
    br_scalar ts;
    br_vector3 side_vector;
    br_angle phi;
    br_matrix34 mat;
    br_transform new_transform;
    br_actor* a;
    br_actor* last_non_ampersand;
    LOG_TRACE("(%d)", pIndex);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropActor0()
void DropActor0() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropActor1()
void DropActor1() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropActor2()
void DropActor2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropActor3()
void DropActor3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropActor4()
void DropActor4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropActor5()
void DropActor5() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropActor6()
void DropActor6() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropActor7()
void DropActor7() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropActor8()
void DropActor8() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropActor9()
void DropActor9() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __cdecl IdentifyAccCB(br_actor *pActor, void *pArg)
br_uint_32 IdentifyAccCB(br_actor* pActor, void* pArg) {
    br_scalar distance;
    char s[256];
    br_vector3 v;
    LOG_TRACE("(%p, %p)", pActor, pArg);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl IdentifyAcc()
void IdentifyAcc() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __cdecl DelGrooveRef(br_actor *pActor, void *pArg)
br_uint_32 DelGrooveRef(br_actor* pActor, void* pArg) {
    tGroovidelic_spec* the_groove;
    int i;
    LOG_TRACE("(%p, %p)", pActor, pArg);
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __cdecl DelReferencedModels(br_actor *pActor, void *pArg)
br_uint_32 DelReferencedModels(br_actor* pActor, void* pArg) {
    tGroovidelic_spec* the_groove;
    int i;
    LOG_TRACE("(%p, %p)", pActor, pArg);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DeleteAcc()
void DeleteAcc() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: br_uint_32 __cdecl OffsetModel(br_actor *pActor, void *pArg)
br_uint_32 OffsetModel(br_actor* pActor, void* pArg) {
    int i;
    LOG_TRACE("(%p, %p)", pActor, pArg);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall OffsetActor(br_actor *pActor@<EAX>, br_vector3 *pOffset@<EDX>)
void OffsetActor(br_actor* pActor, br_vector3* pOffset) {
    LOG_TRACE("(%p, %p)", pActor, pOffset);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall CentreActor(br_actor *pActor@<EAX>, br_vector3 *pOffset@<EDX>)
void CentreActor(br_actor* pActor, br_vector3* pOffset) {
    LOG_TRACE("(%p, %p)", pActor, pOffset);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl SnapAccToVertical()
void SnapAccToVertical() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __usercall RotateAccessory(br_angle pAngle@<EAX>)
void RotateAccessory(br_angle pAngle) {
    br_vector3 mr_offset;
    LOG_TRACE("(%d)", pAngle);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ScaleAccessory(float pScaling_factor)
void ScaleAccessory(float pScaling_factor) {
    br_vector3 mr_offset;
    LOG_TRACE("(%f)", pScaling_factor);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveAccessory(br_scalar pX_shift, br_scalar pY_shift, br_scalar pZ_shift)
void MoveAccessory(br_scalar pX_shift, br_scalar pY_shift, br_scalar pZ_shift) {
    br_vector3 v;
    LOG_TRACE("(%f, %f, %f)", pX_shift, pY_shift, pZ_shift);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateAccL()
void RotateAccL() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateAccL2()
void RotateAccL2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateAccL3()
void RotateAccL3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateAccL4()
void RotateAccL4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateAccR()
void RotateAccR() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateAccR2()
void RotateAccR2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateAccR3()
void RotateAccR3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateAccR4()
void RotateAccR4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl CycleAccRotate()
void CycleAccRotate() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl CycleAccScale()
void CycleAccScale() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ScaleAccUp2()
void ScaleAccUp2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ScaleAccUp3()
void ScaleAccUp3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ScaleAccUp4()
void ScaleAccUp4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ScaleAccDown2()
void ScaleAccDown2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ScaleAccDown3()
void ScaleAccDown3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ScaleAccDown4()
void ScaleAccDown4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXAccL()
void MoveXAccL() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXAccL2()
void MoveXAccL2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXAccL3()
void MoveXAccL3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXAccL4()
void MoveXAccL4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXAccR()
void MoveXAccR() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXAccR2()
void MoveXAccR2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXAccR3()
void MoveXAccR3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXAccR4()
void MoveXAccR4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYAccL()
void MoveYAccL() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYAccL2()
void MoveYAccL2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYAccL3()
void MoveYAccL3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYAccL4()
void MoveYAccL4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYAccR()
void MoveYAccR() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYAccR2()
void MoveYAccR2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYAccR3()
void MoveYAccR3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYAccR4()
void MoveYAccR4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZAccL()
void MoveZAccL() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZAccL2()
void MoveZAccL2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZAccL3()
void MoveZAccL3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZAccL4()
void MoveZAccL4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZAccR()
void MoveZAccR() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZAccR2()
void MoveZAccR2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZAccR3()
void MoveZAccR3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZAccR4()
void MoveZAccR4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: br_material* __cdecl GetInternalMat()
br_material* GetInternalMat() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: br_material* __cdecl GetExternalMat()
br_material* GetExternalMat() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __usercall BuildSpecVolModel(tSpecial_volume *pSpec@<EAX>, int pIndex@<EDX>, br_material *pInt_mat@<EBX>, br_material *pExt_mat@<ECX>)
void BuildSpecVolModel(tSpecial_volume* pSpec, int pIndex, br_material* pInt_mat, br_material* pExt_mat) {
    int i;
    int j;
    int temp;
    tSpecial_volume* v;
    br_actor* actor;
    br_model* model;
    LOG_TRACE("(%p, %d, %p, %p)", pSpec, pIndex, pInt_mat, pExt_mat);
    NOT_IMPLEMENTED();
}

// IDA: void __usercall DropSpecVol(int pIndex@<EAX>)
void DropSpecVol(int pIndex) {
    FILE* f;
    tPath_name the_path;
    int i;
    int spec_count;
    tSpecial_volume spec;
    tSpecial_volume* new_specs;
    char s[256];
    LOG_TRACE("(%d)", pIndex);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropSpecVol0()
void DropSpecVol0() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropSpecVol1()
void DropSpecVol1() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropSpecVol2()
void DropSpecVol2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropSpecVol3()
void DropSpecVol3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropSpecVol4()
void DropSpecVol4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropSpecVol5()
void DropSpecVol5() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropSpecVol6()
void DropSpecVol6() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropSpecVol7()
void DropSpecVol7() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropSpecVol8()
void DropSpecVol8() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DropSpecVol9()
void DropSpecVol9() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl IdentifySpecVol()
void IdentifySpecVol() {
    int i;
    int min_index;
    tSpecial_volume* v;
    br_scalar min_d;
    br_scalar d;
    br_vector3* p;
    char s[256];
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __usercall DelSpecVolumeGraph(int pIndex@<EAX>)
void DelSpecVolumeGraph(int pIndex) {
    br_actor* actor;
    br_model* model;
    LOG_TRACE("(%d)", pIndex);
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl DeleteSpecVol()
void DeleteSpecVol() {
    int index;
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateSpecVolL()
void RotateSpecVolL() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateSpecVolL2()
void RotateSpecVolL2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateSpecVolL3()
void RotateSpecVolL3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateSpecVolL4()
void RotateSpecVolL4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateSpecVolR()
void RotateSpecVolR() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateSpecVolR2()
void RotateSpecVolR2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateSpecVolR3()
void RotateSpecVolR3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl RotateSpecVolR4()
void RotateSpecVolR4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl CycleSpecVolRotate()
void CycleSpecVolRotate() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl CycleSpecVolScale()
void CycleSpecVolScale() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ScaleSpecVolUp2()
void ScaleSpecVolUp2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ScaleSpecVolUp3()
void ScaleSpecVolUp3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ScaleSpecVolUp4()
void ScaleSpecVolUp4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ScaleSpecVolDown2()
void ScaleSpecVolDown2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ScaleSpecVolDown3()
void ScaleSpecVolDown3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ScaleSpecVolDown4()
void ScaleSpecVolDown4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXSpecVolL()
void MoveXSpecVolL() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXSpecVolL2()
void MoveXSpecVolL2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXSpecVolL3()
void MoveXSpecVolL3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXSpecVolL4()
void MoveXSpecVolL4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXSpecVolR()
void MoveXSpecVolR() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXSpecVolR2()
void MoveXSpecVolR2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXSpecVolR3()
void MoveXSpecVolR3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveXSpecVolR4()
void MoveXSpecVolR4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYSpecVolL()
void MoveYSpecVolL() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYSpecVolL2()
void MoveYSpecVolL2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYSpecVolL3()
void MoveYSpecVolL3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYSpecVolL4()
void MoveYSpecVolL4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYSpecVolR()
void MoveYSpecVolR() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYSpecVolR2()
void MoveYSpecVolR2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYSpecVolR3()
void MoveYSpecVolR3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveYSpecVolR4()
void MoveYSpecVolR4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZSpecVolL()
void MoveZSpecVolL() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZSpecVolL2()
void MoveZSpecVolL2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZSpecVolL3()
void MoveZSpecVolL3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZSpecVolL4()
void MoveZSpecVolL4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZSpecVolR()
void MoveZSpecVolR() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZSpecVolR2()
void MoveZSpecVolR2() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZSpecVolR3()
void MoveZSpecVolR3() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl MoveZSpecVolR4()
void MoveZSpecVolR4() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl SnapSpecVolToVertical()
void SnapSpecVolToVertical() {
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl ShowSpecialVolumes()
void ShowSpecialVolumes() {
    int i;
    int j;
    int temp;
    tSpecial_volume* v;
    br_actor* actor;
    br_model* model;
    br_material* internal_mat;
    br_material* external_mat;
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}

// IDA: void __cdecl HideSpecialVolumes()
void HideSpecialVolumes() {
    int i;
    tSpecial_volume* v;
    LOG_TRACE("()");
    NOT_IMPLEMENTED();
}
