#include "newgame.h"
#include "cutscene.h"
#include "displays.h"
#include "drmem.h"
#include "errors.h"
#include "flicplay.h"
#include "globvars.h"
#include "globvrpb.h"
#include "grafdata.h"
#include "graphics.h"
#include "harness/config.h"
#include "harness/hooks.h"
#include "harness/trace.h"
#include "init.h"
#include "input.h"
#include "intrface.h"
#include "loading.h"
#include "network.h"
#include "pd/sys.h"
#include "racestrt.h"
#include "sound.h"
#include "structur.h"
#include "utility.h"
#include "world.h"
#include <stdlib.h>
#include <string.h>

tU8* gFrank_flic_data;
tU8* gAnne_flic_data;
int gNet_storage_allocated;
// clang-format off
tRadio_bastards gRadio_bastards__newgame[11] = { // suffix added to avoid duplicate symbol
    { 1,  32, 0, { 245,   0,   0,   0,   0, }, },
    { 1,  43, 0, { 245,   0,   0,   0,   0, }, },
    { 1,  54, 0, { 245,   0,   0,   0,   0, }, },
    { 1,  65, 0, { 245,   0,   0,   0,   0, }, },
    { 1,  76, 0, { 245,   0,   0,   0,   0, }, },
    { 2,  86, 0, {  81, 150,   0,   0,   0, }, },
    { 2,  97, 0, {  81, 150,   0,   0,   0, }, },
    { 2, 108, 0, {  81, 150,   0,   0,   0, }, },
    { 2, 119, 0, { 150, 201,   0,   0,   0, }, },
    { 4, 130, 0, { 115, 150, 183, 215,   0, }, },
    { 5, 141, 0, { 142, 160, 182, 204, 230, }, },
};
// clang-format on
char _name1[] = "xxxxxxxx.TXT"; // keep compiler happy
char _name2[] = "yyyyyyyy.TXT"; // keep compiler happy
char* gBasic_car_names[2] = { _name1, _name2 };
tNet_game_options gNet_settings[8];
tJoinable_game gGames_to_join[6];
tNet_game_options* gOptions;
int gNet_target[7];
int gLast_graph_sel__newgame;                   // suffix added to avoid duplicate symbol
tInterface_spec* gThe_interface_spec__newgame;  // suffix added to avoid duplicate symbol
tNet_sequence_type gNet_race_sequence__newgame; // suffix added to avoid duplicate symbol
tNet_game_type gLast_game_type;
int gCurrent_net_game_count;
tU32 gAnne_flic_data_length;
int gShifted_default_yet;
char* gNet_name;
tU32 gFrank_flic_data_length;
int gLast_net_choose_box;
int gCurrent_game_selection;
int gRace_index;
int gRadio_selected;

// IDA: void __cdecl StartRollingPlayerNamesIn()
void StartRollingPlayerNamesIn(void) {
    int i;
    LOG_TRACE("()");

    for (i = 0; i < COUNT_OF(gCurrent_graf_data->player_name_x); i++) {
        SetSlotXY(i, gCurrent_graf_data->player_name_x[i], gCurrent_graf_data->player_name_y);
        AddRollingString(gProgram_state.player_name[i], gCurrent_graf_data->player_name_x[i], gCurrent_graf_data->player_name_y, eRT_alpha);
    }
}

// IDA: void __cdecl FrankAnneStart1()
void FrankAnneStart1(void) {
    LOG_TRACE("()");

    StartRollingPlayerNamesIn();
    if (gFrank_flic_data == NULL) {
        if (!LoadFlicData("FRANK.FLI", &gFrank_flic_data, &gFrank_flic_data_length)) {
            FatalError(kFatalError_LoadOpponentMugShotFile);
        }
    } else {
        MAMSLock((void**)&gFrank_flic_data);
    }
    if (!gAnne_flic_data) {
        if (!LoadFlicData("ANNIE.FLI", &gAnne_flic_data, &gAnne_flic_data_length)) {
            FatalError(kFatalError_LoadOpponentMugShotFile);
        }
    } else {
        MAMSLock((void**)&gAnne_flic_data);
    }
    InitialiseFlicPanel(0,
        gCurrent_graf_data->frank_panel_left,
        gCurrent_graf_data->frank_panel_top,
        gCurrent_graf_data->frank_panel_right - gCurrent_graf_data->frank_panel_left,
        gCurrent_graf_data->frank_panel_bottom - gCurrent_graf_data->frank_panel_top);
    InitialiseFlicPanel(1,
        gCurrent_graf_data->anne_panel_left,
        gCurrent_graf_data->anne_panel_top,
        gCurrent_graf_data->anne_panel_right - gCurrent_graf_data->anne_panel_left,
        gCurrent_graf_data->anne_panel_bottom - gCurrent_graf_data->anne_panel_top);
}

// IDA: void __cdecl FrankAnneStart2()
void FrankAnneStart2(void) {
    LOG_TRACE("()");

    ChangePanelFlic(0, gFrank_flic_data, gFrank_flic_data_length);
    ChangePanelFlic(1, gAnne_flic_data, gAnne_flic_data_length);
    TellyInImage(GetPanelPixelmap(0), gCurrent_graf_data->frank_panel_left, gCurrent_graf_data->frank_panel_top);
    TellyInImage(GetPanelPixelmap(1), gCurrent_graf_data->anne_panel_left, gCurrent_graf_data->anne_panel_top);
}

// IDA: void __usercall GetPlayerName(int pStarting_to_type@<EAX>, int pCurrent_choice@<EDX>, char *pString@<EBX>, int *pMax_length@<ECX>)
void GetPlayerName(int pStarting_to_type, int pCurrent_choice, char* pString, int* pMax_length) {
    LOG_TRACE("(%d, %d, \"%s\", %p)", pStarting_to_type, pCurrent_choice, pString, pMax_length);

    strcpy(pString, gProgram_state.player_name[pCurrent_choice]);
    *pMax_length = PLAYER_NAME_MAX_LENGTH;
}

// IDA: int __usercall FrankAnneDone@<EAX>(int pCurrent_choice@<EAX>, int pCurrent_mode@<EDX>, int pGo_ahead@<EBX>, int pEscaped@<ECX>, int pTimed_out)
int FrankAnneDone(int pCurrent_choice, int pCurrent_mode, int pGo_ahead, int pEscaped, int pTimed_out) {
    LOG_TRACE("(%d, %d, %d, %d, %d)", pCurrent_choice, pCurrent_mode, pGo_ahead, pEscaped, pTimed_out);

    RemoveTransientBitmaps(1);
    TellyOutImage(GetPanelPixelmap(1), gCurrent_graf_data->anne_panel_left, gCurrent_graf_data->anne_panel_top);
    TellyOutImage(GetPanelPixelmap(0), gCurrent_graf_data->frank_panel_left, gCurrent_graf_data->frank_panel_top);
    if (gFrank_flic_data) {
        MAMSUnlock((void**)&gFrank_flic_data);
    }
    if (gAnne_flic_data) {
        MAMSUnlock((void**)&gAnne_flic_data);
    }
    gProgram_state.frank_or_anniness = pCurrent_choice;
    GetTypedName(gProgram_state.player_name[pCurrent_choice], PLAYER_NAME_MAX_LENGTH);
    return pCurrent_choice;
}

// IDA: void __usercall FrankAnneDraw(int pCurrent_choice@<EAX>, int pCurrent_mode@<EDX>)
void FrankAnneDraw(int pCurrent_choice, int pCurrent_mode) {
    LOG_TRACE9("(%d, %d)", pCurrent_choice, pCurrent_mode);

    if (gTyping) {
        if (GetTotalTime() & 0x100) {
            if (pCurrent_choice < 2) {
                TransDRPixelmapText(
                    gBack_screen,
                    gCurrent_graf_data->enter_name_x[pCurrent_choice],
                    gCurrent_graf_data->enter_name_y,
                    &gFonts[FONT_GRNLIT],
                    GetMiscString(kMiscString_ENTER_NAME),
                    gBack_screen->width);
            }
        }
    }
}

// IDA: int __cdecl FrankieOrAnnie()
int FrankieOrAnnie(void) {
    static tFlicette flicker_on[3] = {
        { 83, { 61, 122 }, { 52, 125 } },
        { 83, { 184, 398 }, { 52, 125 } },
        { 43, { 215, 430 }, { 158, 379 } }
    };
    static tFlicette flicker_off[3] = {
        { 82, { 61, 122 }, { 52, 125 } },
        { 82, { 184, 398 }, { 52, 125 } },
        { 42, { 215, 430 }, { 158, 379 } }
    };
    static tFlicette push[3] = {
        { 83, { 61, 122 }, { 52, 125 } },
        { 83, { 184, 398 }, { 52, 125 } },
        { 45, { 215, 430 }, { 158, 379 } }
    };
    static tMouse_area mouse_areas[3] = {
        { { 55, 110 }, { 52, 125 }, { 161, 322 }, { 154, 370 }, 0, 0, 0, NULL },
        { { 178, 356 }, { 52, 125 }, { 295, 596 }, { 154, 370 }, 1, 0, 0, NULL },
        { { 215, 430 }, { 158, 379 }, { 278, 556 }, { 179, 430 }, 2, 1, 1, NULL }
    };
    static tRectile recopy_areas[2] = {
        { { 55, 110 }, { 132, 317 }, { 161, 322 }, { 154, 370 } },
        { { 178, 356 }, { 132, 317 }, { 295, 590 }, { 154, 370 } }
    };
    static tInterface_spec interface_spec = {
        0,               // initial_imode
        80,              // first_opening_flic
        0,               // second_opening_flic
        81,              // end_flic_go_ahead
        81,              // end_flic_escaped
        81,              // end_flic_otherwise
        0,               // flic_bunch_to_load
        { -1, -1 },      // move_left_new_mode
        { -1, 0 },       // move_left_delta
        { 0, 2 },        // move_left_min
        { 1, 2 },        // move_left_max
        { NULL, NULL },  // move_left_proc
        { -1, -1 },      // move_right_new_mode
        { 1, 0 },        // move_right_delta
        { 0, 2 },        // move_right_min
        { 1, 2 },        // move_right_max
        { NULL, NULL },  // move_right_proc
        { 1, 0 },        // move_up_new_mode
        { -2, -1 },      // move_up_delta
        { 2, 1 },        // move_up_min
        { 2, 1 },        // move_up_max
        { NULL, NULL },  // move_up_proc
        { 1, 0 },        // move_down_new_mode
        { 2, -1 },       // move_down_delta
        { 2, 1 },        // move_down_min
        { 2, 1 },        // move_down_max
        { NULL, NULL },  // move_down_proc
        { 1, 1 },        // go_ahead_allowed
        { NULL, NULL },  // go_ahead_proc
        { 1, 1 },        // escape_allowed
        { NULL, NULL },  // escape_proc
        NULL,            // exit_proc
        FrankAnneDraw,   // draw_proc
        0,               // time_out
        FrankAnneStart1, // start_proc1
        FrankAnneStart2, // start_proc2
        FrankAnneDone,   // done_proc
        1,               // font_needed
        { 1, 0 },        // typeable
        GetPlayerName,   // get_original_string
        2,               // escape_code
        1,               // dont_save_or_load
        3,               // number_of_button_flics
        flicker_on,      // flicker_on_flics
        flicker_off,     // flicker_off_flics
        push,            // pushed_flics
        3,               // number_of_mouse_areas
        mouse_areas,     // mouse_areas
        2,               // number_of_recopy_areas
        recopy_areas     // recopy_areas
    };

    int result;
    LOG_TRACE("()");

    LoadFont(FONT_GRNLIT);
    result = DoInterfaceScreen(&interface_spec, 0, gProgram_state.frank_or_anniness);
    DisposeFlicPanel(1);
    DisposeFlicPanel(0);
    DisposeFont(FONT_GRNLIT);
    return result < 2;
}

// IDA: int __cdecl SelectSkillLevel()
int SelectSkillLevel(void) {
    static tFlicette flicker_on[4] = {
        { 116, { 38, 76 }, { 55, 132 } },
        { 119, { 36, 72 }, { 83, 199 } },
        { 121, { 38, 76 }, { 111, 266 } },
        { 43, { 227, 454 }, { 158, 379 } }
    };

    static tFlicette flicker_off[4] = {
        { 115, { 38, 76 }, { 55, 132 } },
        { 118, { 36, 72 }, { 83, 199 } },
        { 120, { 38, 76 }, { 111, 266 } },
        { 42, { 227, 454 }, { 158, 379 } }
    };

    static tFlicette push[4] = {
        { 117, { 38, 76 }, { 55, 132 } },
        { 117, { 36, 72 }, { 83, 199 } },
        { 117, { 38, 76 }, { 111, 266 } },
        { 45, { 227, 454 }, { 158, 379 } }
    };
    static tMouse_area mouse_areas[4] = {
        { { 38, 76 }, { 55, 132 }, { 205, 410 }, { 69, 166 }, 0, 0, 0, NULL },
        { { 36, 72 }, { 83, 199 }, { 205, 410 }, { 98, 235 }, 1, 0, 0, NULL },
        { { 38, 76 }, { 111, 266 }, { 205, 410 }, { 125, 300 }, 2, 0, 0, NULL },
        { { 227, 454 }, { 158, 379 }, { 290, 580 }, { 178, 427 }, 3, 0, 0, NULL }
    };

    static tInterface_spec interface_spec = {
        0,              // initial_imode
        110,            // first_opening_flic
        0,              // second_opening_flic
        -1,             // end_flic_go_ahead
        111,            // end_flic_escaped
        -1,             // end_flic_otherwise
        0,              // flic_bunch_to_load
        { -1, 0 },      // move_left_new_mode
        { 0, 0 },       // move_left_delta
        { 0, 0 },       // move_left_min
        { 0, 0 },       // move_left_max
        { NULL, NULL }, // move_left_proc
        { -1, 0 },      // move_right_new_mode
        { 0, 0 },       // move_right_delta
        { 0, 0 },       // move_right_min
        { 0, 0 },       // move_right_max
        { NULL, NULL }, // move_right_proc
        { -1, 0 },      // move_up_new_mode
        { -1, 0 },      // move_up_delta
        { 0, 0 },       // move_up_min
        { 3, 0 },       // move_up_max
        { NULL, NULL }, // move_up_proc
        { -1, 0 },      // move_down_new_mode
        { 1, 0 },       // move_down_delta
        { 0, 0 },       // move_down_min
        { 3, 0 },       // move_down_max
        { NULL, NULL }, // move_down_proc
        { 1, 1 },       // go_ahead_allowed
        { NULL, NULL }, // go_ahead_proc
        { 1, 1 },       // escape_allowed
        { NULL, NULL }, // escape_proc
        NULL,           // exit_proc
        NULL,           // draw_proc
        0u,             // time_out
        NULL,           // start_proc1
        NULL,           // start_proc2
        NULL,           // done_proc
        0,              // font_needed
        { 0, 0 },       // typeable
        NULL,           // get_original_string
        3,              // escape_code
        1,              // dont_save_or_load
        4,              // number_of_button_flics
        flicker_on,     // flicker_on_flics
        flicker_off,    // flicker_off_flics
        push,           // pushed_flics
        4,              // number_of_mouse_areas
        mouse_areas,    // mouse_areas
        0,              // number_of_recopy_areas
        NULL            // recopy_areas
    };

    int result;
    LOG_TRACE("()");

    result = DoInterfaceScreen(&interface_spec, 0, gProgram_state.skill_level);
    if (result > 2) {
        return 0;
    }
    gProgram_state.skill_level = result;
    return 1;
}

// IDA: int __cdecl DoOnePlayerStart()
int DoOnePlayerStart(void) {
    int merrily_looping;
    tProgram_state saved_state;
    LOG_TRACE("()");

    if (OriginalCarmaCDinDrive()) {
        memcpy(&saved_state, &gProgram_state, sizeof(tProgram_state));
        do {
            merrily_looping = FrankieOrAnnie();
            if (!merrily_looping) {
                memcpy(&gProgram_state, &saved_state, sizeof(tProgram_state));
                return 0;
            }
            if ((harness_game_info.mode == eGame_carmageddon_demo || harness_game_info.mode == eGame_splatpack_demo || harness_game_info.mode == eGame_splatpack_xmas_demo) && gProgram_state.frank_or_anniness != eFrankie) {
                DoFeatureUnavailableInDemo();
                memset(&gProgram_state, 0, sizeof(gProgram_state));
                return 0;
            }

            if (SelectSkillLevel()) {
                DoGoToRaceAnimation();
                StartLoadingScreen();
                AboutToLoadFirstCar();
                PrintMemoryDump(0, "JUST BEFORE LOADING YOUR CAR");
                SwitchToRealResolution();
                LoadCar(
                    gBasic_car_names[gProgram_state.frank_or_anniness],
                    eDriver_local_human,
                    &gProgram_state.current_car,
                    gProgram_state.frank_or_anniness,
                    gProgram_state.player_name[gProgram_state.frank_or_anniness],
                    &gOur_car_storage_space);
                SwitchToLoresMode();
                SetCarStorageTexturingLevel(&gOur_car_storage_space, GetCarTexturingLevel(), eCTL_full);
                PrintMemoryDump(0, "IMMEDIATELY AFTER LOADING YOUR CAR");
                gNet_mode = eNet_mode_none;
                InitGame(0);
                merrily_looping = 0;
            }
        } while (merrily_looping);
        UnlockBunchOfFlics(4);
        return 1;
    } else {
        DoErrorInterface(kMiscString_PLEASE_INSERT_THE_CARMAGEDDON_CD);
        return 0;
    }
}

// IDA: int __usercall NewNetGameUp@<EAX>(int *pCurrent_choice@<EAX>, int *pCurrent_mode@<EDX>)
int NewNetGameUp(int* pCurrent_choice, int* pCurrent_mode) {
    int new_sel;
    int i;
    LOG_TRACE("(%p, %p)", pCurrent_choice, pCurrent_mode);

    DRS3StartSound(gEffects_outlet, 3000);
    if (*pCurrent_mode == 0) {
        gLast_graph_sel__newgame = COUNT_OF(gGames_to_join);
    }
    new_sel = -1;
    for (i = gLast_graph_sel__newgame - 1; i >= 0; i--) {
        gLast_graph_sel__newgame = i;
        if (gGames_to_join[i].game == NULL) {
            continue;
        }
        if (!gGames_to_join[i].game->options.open_game && !gGames_to_join[i].game->no_races_yet) {
            continue;
        }
        if (gGames_to_join[i].game->num_players > 5) {
            continue;
        }
        new_sel = i;
        break;
    }
    if (new_sel < 0) {
        gLast_graph_sel__newgame = -1;
        *pCurrent_choice = 0;
        *pCurrent_mode = 0;
    } else {
        gLast_graph_sel__newgame = new_sel;
        *pCurrent_choice = 2;
        *pCurrent_mode = 1;
    }
    return 1;
}

// IDA: int __usercall NewNetGameDown@<EAX>(int *pCurrent_choice@<EAX>, int *pCurrent_mode@<EDX>)
int NewNetGameDown(int* pCurrent_choice, int* pCurrent_mode) {
    int new_sel;
    int i;
    LOG_TRACE("(%p, %p)", pCurrent_choice, pCurrent_mode);

    DRS3StartSound(gEffects_outlet, 3000);
    if (*pCurrent_mode == 0) {
        gLast_graph_sel__newgame = -1;
    }

    new_sel = -1;
    for (i = gLast_graph_sel__newgame - 1; i < COUNT_OF(gGames_to_join); i++) {
        gLast_graph_sel__newgame = i;
        if (gGames_to_join[i].game == NULL) {
            continue;
        }
        if (!gGames_to_join[i].game->options.open_game && !gGames_to_join[i].game->no_races_yet) {
            continue;
        }
        if (gGames_to_join[i].game->num_players > 5) {
            continue;
        }
        new_sel = i;
        break;
    }
    if (new_sel < 0) {
        gLast_graph_sel__newgame = -1;
        *pCurrent_choice = 0;
        *pCurrent_mode = 0;
    } else {
        gLast_graph_sel__newgame = new_sel;
        *pCurrent_choice = 2;
        *pCurrent_mode = 1;
    }
    return 1;
}

// IDA: void __usercall DisposeJoinableGame(int pIndex@<EAX>)
void DisposeJoinableGame(int pIndex) {
    LOG_TRACE("(%d)", pIndex);

    NetDisposeGameDetails(gGames_to_join[pIndex].game);
    gGames_to_join[pIndex].game = NULL;
}

// IDA: void __usercall DrawAnItem(int pX@<EAX>, int pY_index@<EDX>, int pFont_index@<EBX>, char *pText@<ECX>)
void DrawAnItem__newgame(int pX, int pY_index, int pFont_index, char* pText) {
    LOG_TRACE("(%d, %d, %d, \"%s\")", pX, pY_index, pFont_index, pText);

    TransDRPixelmapText(gBack_screen,
        pX,
        gCurrent_graf_data->joinable_games_y + gCurrent_graf_data->joinable_games_y_pitch * pY_index,
        &gFonts[pFont_index],
        pText,
        pX + DRTextWidth(&gFonts[pFont_index], pText));
}

// IDA: void __usercall DrawColumnHeading(int pStr_index@<EAX>, int pX@<EDX>)
void DrawColumnHeading__newgame(int pStr_index, int pX) {
    LOG_TRACE("(%d, %d)", pStr_index, pX);

    TransDRPixelmapText(gBack_screen,
        pX,
        gCurrent_graf_data->joinable_games_y - gCurrent_graf_data->joinable_games_y_pitch,
        &gFonts[kFont_GRYLIT],
        GetMiscString(pStr_index),
        pX + DRTextWidth(&gFonts[kFont_GRYLIT], GetMiscString(pStr_index)));
}

// IDA: void __usercall DrawGames(int pCurrent_choice@<EAX>, int pCurrent_mode@<EDX>)
void DrawGames(int pCurrent_choice, int pCurrent_mode) {
    int i;
    int font_index;
    int current_index;
    int x_coord;
    int y_coord;
    char s[256];
    char* s2;
    char* s3;
    LOG_TRACE("(%d, %d)", pCurrent_choice, pCurrent_mode);

    current_index = 0;
    BrPixelmapRectangleFill(gBack_screen,
        gCurrent_graf_data->joinable_games_sel_left,
        gCurrent_graf_data->joinable_games_sel_top_marg + gCurrent_graf_data->joinable_games_y,
        gCurrent_graf_data->joinable_games_sel_right - gCurrent_graf_data->joinable_games_sel_left,
        (COUNT_OF(gGames_to_join) + 1) * gCurrent_graf_data->joinable_games_y_pitch + gCurrent_graf_data->joinable_games_sel_bot_marg,
        0);
    if (gMouse_in_use && pCurrent_mode) {
        GetMousePosition(&x_coord, &y_coord);
        gLast_graph_sel__newgame = -1;
    }
    DrawColumnHeading__newgame(kMiscString_NetworkGamesTableHeading_HOST, gCurrent_graf_data->joinable_games_x_1);
    DrawColumnHeading__newgame(kMiscString_NetworkGamesTableHeading_GAME_TYPE, gCurrent_graf_data->joinable_games_x_2);
    DrawColumnHeading__newgame(kMiscString_NetworkGamesTableHeading_PLAYERS, gCurrent_graf_data->joinable_games_x_3);
    DrawColumnHeading__newgame(kMiscString_NetworkGamesTableHeading_STATUS, gCurrent_graf_data->joinable_games_x_4);
    BrPixelmapLine(gBack_screen,
        gCurrent_graf_data->joinable_games_sel_left,
        gCurrent_graf_data->joinable_games_y + gFonts[kFont_GRYLIT].height + 1 - (TranslationMode() ? 2 : 0) - gCurrent_graf_data->joinable_games_y_pitch,
        gCurrent_graf_data->joinable_games_sel_right - 1,
        gCurrent_graf_data->joinable_games_y + gFonts[kFont_GRYLIT].height + 1 - (TranslationMode() ? 2 : 0) - gCurrent_graf_data->joinable_games_y_pitch,
        6);
    for (i = 0; i < COUNT_OF(gGames_to_join); i++) {
        if (gGames_to_join[i].game == NULL) {
            continue;
        }
        if (gGames_to_join[i].game->type < 0 || gGames_to_join[i].game->type >= eNet_game_type_count) {
            continue;
        }
        if ((PDGetTotalTime() - gGames_to_join[i].time) >= 15000) {
            DisposeJoinableGame(i);
            continue;
        }
        if (gMouse_in_use
            && pCurrent_mode != 0
            && gGames_to_join[i].game != NULL
            && (gGames_to_join[i].game->options.open_game || gGames_to_join[i].game->no_races_yet)
            && gGames_to_join[i].game->num_players <= 5
            && x_coord >= gCurrent_graf_data->joinable_games_sel_left
            && x_coord <= gCurrent_graf_data->joinable_games_sel_right
            && y_coord >= (gCurrent_graf_data->joinable_games_y + gCurrent_graf_data->joinable_games_sel_top_marg + current_index * gCurrent_graf_data->joinable_games_y_pitch)
            && y_coord <= (gCurrent_graf_data->joinable_games_y + gCurrent_graf_data->joinable_games_sel_bot_marg + current_index * gCurrent_graf_data->joinable_games_y_pitch - 1)) {
            gLast_graph_sel__newgame = i;
        }
        if (i == gLast_graph_sel__newgame) {
            font_index = 10;
        } else {
            font_index = 9;
        }
        sprintf(s, "%s", gGames_to_join[i].game->host_name);
        DrawAnItem__newgame(gCurrent_graf_data->joinable_games_x_1, current_index, font_index, s);
        sprintf(s, "%s", GetMiscString(kMiscString_NetworkGameTypeNames_START + gGames_to_join[i].game->type));
        DrawAnItem__newgame(gCurrent_graf_data->joinable_games_x_2, current_index, font_index, s);
        sprintf(s, "%d", gGames_to_join[i].game->num_players);
        DrawAnItem__newgame(gCurrent_graf_data->joinable_games_x_3, current_index, font_index, s);
        sprintf(s, "%s, %s",
            GetMiscString(kMiscString_NetworkGameStage_START + gGames_to_join[i].game->status.stage),
            GetMiscString(kMiscString_NetworkGameOpenGame_START + gGames_to_join[i].game->options.open_game));
        DrawAnItem__newgame(gCurrent_graf_data->joinable_games_x_4, current_index, font_index, s);
        if (i == gLast_graph_sel__newgame) {
            DrawRectangle(gBack_screen,
                gCurrent_graf_data->joinable_games_sel_left,
                gCurrent_graf_data->joinable_games_y + gCurrent_graf_data->joinable_games_sel_top_marg + gCurrent_graf_data->joinable_games_y_pitch * current_index,
                gCurrent_graf_data->joinable_games_sel_right - 1,
                gCurrent_graf_data->joinable_games_y + gCurrent_graf_data->joinable_games_sel_bot_marg + gCurrent_graf_data->joinable_games_y_pitch * current_index - 1,
                45);
        }
        current_index++;
    }

    if (current_index != 0 && (gCurrent_game_selection == 0 || (gLast_graph_sel__newgame >= 0 && (gGames_to_join[gLast_graph_sel__newgame].game == NULL || (!gGames_to_join[gLast_graph_sel__newgame].game->options.open_game && !gGames_to_join[gLast_graph_sel__newgame].game->no_races_yet) || gGames_to_join[gLast_graph_sel__newgame].game->num_players > 5)))) {
        gCurrent_game_selection = 1;
        for (i = 0; i < COUNT_OF(gGames_to_join); i++) {
            if (gGames_to_join[i].game != NULL && (gGames_to_join[i].game->options.open_game || gGames_to_join[i].game->no_races_yet) && gGames_to_join[i].game->num_players <= 5) {
                gLast_graph_sel__newgame = i;
                ChangeSelectionTo(2, 1);
                return;
            }
        }
    }
    if (pCurrent_mode != 0 &&
#if defined(DETHRACE_FIX_BUGS)
        (gLast_graph_sel__newgame >= 0) &&
#endif
        (current_index == 0
            || gGames_to_join[gLast_graph_sel__newgame].game == NULL
            || (!gGames_to_join[gLast_graph_sel__newgame].game->options.open_game && !gGames_to_join[gLast_graph_sel__newgame].game->no_races_yet)
            || gGames_to_join[gLast_graph_sel__newgame].game->num_players > 5)) {
        gLast_graph_sel__newgame = -1;
        ChangeSelectionTo(0, 0);
    }
}

// IDA: void __cdecl InitGamesToJoin()
void InitGamesToJoin(void) {
    int i;
    LOG_TRACE("()");

    for (i = 0; i < COUNT_OF(gGames_to_join); i++) {
        gGames_to_join[i].game = NULL;
    }
    gLast_graph_sel__newgame = -1;
}

// IDA: void __usercall DisposeJoinList(int pExemption@<EAX>)
void DisposeJoinList(int pExemption) {
    int i;
    LOG_TRACE("(%d)", pExemption);

    for (i = 0; i < COUNT_OF(gGames_to_join); i++) {
        if (i == pExemption) {
            continue;
        }
        if (gGames_to_join[i].game != NULL) {
            DisposeJoinableGame(i);
        }
    }
}

// IDA: void __usercall AddToJoinList(tNet_game_details *pGame@<EAX>)
void AddToJoinList(tNet_game_details* pGame) {
    int i;
    int slot_to_use;
    tU32 this_game_ID;
    LOG_TRACE("(%p)", pGame);

    slot_to_use = -1;
    this_game_ID = NetExtractGameID(pGame);
    for (i = 0; i < COUNT_OF(gGames_to_join); i++) {
        if (gGames_to_join[i].game != NULL) {
            if (NetExtractGameID(gGames_to_join[i].game) == this_game_ID) {
                DisposeJoinableGame(i);
                slot_to_use = i;
                break;
            }
        }
    }
    if (slot_to_use < 0) {
        for (i = 0; i < COUNT_OF(gGames_to_join); i++) {
            if (gGames_to_join[i].game == NULL) {
                slot_to_use = i;
                break;
            }
        }
    }
    if (slot_to_use >= 0) {
        gGames_to_join[slot_to_use].game = pGame;
        gGames_to_join[slot_to_use].time = PDGetTotalTime();
    }
}

// IDA: void __cdecl NewNetStart1()
void NewNetStart1(void) {
    LOG_TRACE("()");

    gTyping = 1;
    SetSlotXY(0, gCurrent_graf_data->net_player_name_x, gCurrent_graf_data->net_player_name_y);
    AddRollingString(gNet_player_name,
        gCurrent_graf_data->net_player_name_x, gCurrent_graf_data->net_player_name_y, eRT_alpha);
}

// IDA: void __cdecl NewNetStart2()
void NewNetStart2(void) {
    LOG_TRACE("()");

    gTyping_slot = 0;
    StartTyping(0, gNet_player_name, 11);
}

// IDA: void __usercall NewNetGetName(int pStarting_to_type@<EAX>, int pCurrent_choice@<EDX>, char *pString@<EBX>, int *pMax_length@<ECX>)
void NewNetGetName(int pStarting_to_type, int pCurrent_choice, char* pString, int* pMax_length) {
    LOG_TRACE("(%d, %d, \"%s\", %p)", pStarting_to_type, pCurrent_choice, pString, pMax_length);

    strcpy(pString, gNet_player_name);
    *pMax_length = 10;
}

// IDA: int __usercall NewNetDone@<EAX>(int pCurrent_choice@<EAX>, int pCurrent_mode@<EDX>, int pGo_ahead@<EBX>, int pEscaped@<ECX>, int pTimed_out)
int NewNetDone(int pCurrent_choice, int pCurrent_mode, int pGo_ahead, int pEscaped, int pTimed_out) {
    LOG_TRACE("(%d, %d, %d, %d, %d)", pCurrent_choice, pCurrent_mode, pGo_ahead, pEscaped, pTimed_out);

    if (pCurrent_choice != 1) {
        GetTypedName(gNet_player_name, 10);
    }
    return pCurrent_choice;
}

// IDA: int __usercall NewNetGoAhead@<EAX>(int *pCurrent_choice@<EAX>, int *pCurrent_mode@<EDX>)
int NewNetGoAhead(int* pCurrent_choice, int* pCurrent_mode) {
    LOG_TRACE("(%p, %p)", pCurrent_choice, pCurrent_mode);

    return gLast_graph_sel__newgame >= 0;
}

// IDA: tJoin_or_host_result __usercall JoinOrHostGame@<EAX>(tNet_game_details **pGame_to_join@<EAX>)
tJoin_or_host_result JoinOrHostGame(tNet_game_details** pGame_to_join) {
    static tFlicette flicker_on[2] = {
        { 43, { 41, 122 }, { 164, 370 } },
        { 43, { 230, 440 }, { 164, 370 } },
    };
    static tFlicette flicker_off[2] = {
        { 42, { 41, 122 }, { 164, 370 } },
        { 42, { 230, 440 }, { 164, 370 } },
    };
    static tFlicette push[2] = {
        { 90, { 41, 122 }, { 164, 370 } },
        { 45, { 230, 440 }, { 164, 370 } },
    };
    static tMouse_area mouse_areas[3] = {
        { { 41, 122 }, { 164, 370 }, { 104, 326 }, { 184, 422 }, 0, 0, 1, NULL },
        { { 230, 440 }, { 164, 370 }, { 293, 568 }, { 184, 422 }, 1, 0, 1, NULL },
        { { 42, 94 }, { 57, 137 }, { 290, 556 }, { 150, 341 }, 2, 1, 1, NULL },
    };
    static tRectile recopy_areas[1] = {
        {
            { 124, 110 },
            { 163, 317 },
            { 212, 322 },
            { 180, 370 },
        },
    };
    static tInterface_spec interface_spec = {
        0,
        100,
        0,
        101,
        101,
        101,
        -1,
        { 0, -1 },
        { -1, -1 },
        { 0, 2 },
        { 1, 2 },
        { NULL, NULL },
        { 0, -1 },
        { 1, -1 },
        { 0, 2 },
        { 1, 2 },
        { NULL, NULL },
        { 1, -1 },
        { 0, 0 },
        { 2, 0 },
        { 2, 0 },
        { NewNetGameUp, NewNetGameUp },
        { 1, -1 },
        { 0, 0 },
        { 2, 0 },
        { 2, 0 },
        { NewNetGameDown, NewNetGameDown },
        { 1, 1 },
        { NULL, NewNetGoAhead },
        { 1, 1 },
        { NULL, NULL },
        NULL,
        DrawGames,
        0,
        NewNetStart1,
        NewNetStart2,
        NewNetDone,
        1,
        { 1, 1 },
        NewNetGetName,
        1,
        1,
        COUNT_OF(flicker_on),
        flicker_on,
        flicker_off,
        push,
        COUNT_OF(mouse_areas),
        mouse_areas,
        COUNT_OF(recopy_areas),
        recopy_areas,
    };
    int result;
    LOG_TRACE("(%p)", pGame_to_join);

    gCurrent_game_selection = 0;
    LoadFont(kFont_GRNDK);
    LoadFont(kFont_GRNLIT);
    LoadFont(kFont_GREENHED);
    LoadFont(kFont_GRYLIT);
    SetAlwaysTyping();
    InitGamesToJoin();
    NetStartProducingJoinList(AddToJoinList);
    result = DoInterfaceScreen(&interface_spec, 0, 0);
    ClearAlwaysTyping();
    NetEndJoinList();
    DisposeJoinList(gLast_graph_sel__newgame);
    DisposeFont(9);
    DisposeFont(10);
    DisposeFont(3);
    DisposeFont(12);
    strcpy(gProgram_state.player_name[0], gNet_player_name);
    SaveOptions();
    switch (result) {
    case 0:
        return eJoin_or_host_host;
    case 1:
        return eJoin_or_host_cancel;
    case 2:
        *pGame_to_join = gGames_to_join[gLast_graph_sel__newgame].game;
        return eJoin_or_host_join;
    default:
        return eJoin_or_host_cancel;
    }
}

// IDA: void __usercall GetNetOptions(tNet_game_options *pGame_options@<EAX>)
void GetNetOptions(tNet_game_options* pGame_options) {
    LOG_TRACE("(%p)", pGame_options);

    pGame_options->enable_text_messages = gRadio_bastards__newgame[0].current_value;
    pGame_options->show_players_on_map = gRadio_bastards__newgame[1].current_value;
    pGame_options->show_peds_on_map = gRadio_bastards__newgame[2].current_value;
    pGame_options->show_powerups_on_map = gRadio_bastards__newgame[3].current_value;
    pGame_options->powerup_respawn = gRadio_bastards__newgame[4].current_value;
    pGame_options->open_game = !gRadio_bastards__newgame[5].current_value;
    pGame_options->grid_start = !gRadio_bastards__newgame[6].current_value;
    pGame_options->race_sequence_type = gRadio_bastards__newgame[7].current_value ? eNet_sequence_sequential : eNet_sequence_random;
    pGame_options->random_car_choice = gRadio_bastards__newgame[8].current_value;
    pGame_options->car_choice = gRadio_bastards__newgame[9].current_value;
    pGame_options->starting_money_index = gRadio_bastards__newgame[10].current_value;
}

// IDA: void __usercall SetNetOptions(tNet_game_options *pGame_options@<EAX>)
void SetNetOptions(tNet_game_options* pGame_options) {
    LOG_TRACE("(%p)", pGame_options);

    gRadio_bastards__newgame[0].current_value = pGame_options->enable_text_messages;
    gRadio_bastards__newgame[1].current_value = pGame_options->show_players_on_map;
    gRadio_bastards__newgame[2].current_value = pGame_options->show_peds_on_map;
    gRadio_bastards__newgame[3].current_value = pGame_options->show_powerups_on_map;
    gRadio_bastards__newgame[4].current_value = pGame_options->powerup_respawn;
    gRadio_bastards__newgame[5].current_value = !pGame_options->open_game;
    gRadio_bastards__newgame[6].current_value = !pGame_options->grid_start;
    gRadio_bastards__newgame[7].current_value = pGame_options->race_sequence_type == eNet_sequence_sequential;
    gRadio_bastards__newgame[8].current_value = pGame_options->random_car_choice;
    gRadio_bastards__newgame[9].current_value = pGame_options->car_choice;
    gRadio_bastards__newgame[10].current_value = pGame_options->starting_money_index;
}

// IDA: void __usercall NetPlayCheckboxOn2(int pIndex@<EAX>)
void NetPlayCheckboxOn2(int pIndex) {
    LOG_TRACE("(%d)", pIndex);

    RunFlicAt(95,
        gRadio_bastards__newgame[pIndex].left[0],
        gRadio_bastards__newgame[pIndex].top + 1);
}

// IDA: void __usercall NetPlayCheckboxOff2(int pIndex@<EAX>)
void NetPlayCheckboxOff2(int pIndex) {
    LOG_TRACE("(%d)", pIndex);

    RunFlicAt(96,
        gRadio_bastards__newgame[pIndex].left[0],
        gRadio_bastards__newgame[pIndex].top + 1);
}

// IDA: void __usercall NetPlayCheckboxOn(int pIndex@<EAX>)
void NetPlayCheckboxOn(int pIndex) {
    LOG_TRACE("(%d)", pIndex);

    RemoveTransientBitmaps(1);
    DontLetFlicFuckWithPalettes();
    TurnFlicTransparencyOn();
    NetPlayCheckboxOn2(pIndex);
    TurnFlicTransparencyOff();
    LetFlicFuckWithPalettes();
}

// IDA: void __usercall NetPlayCheckboxOff(int pIndex@<EAX>)
void NetPlayCheckboxOff(int pIndex) {
    LOG_TRACE("(%d)", pIndex);

    RemoveTransientBitmaps(1);
    DontLetFlicFuckWithPalettes();
    TurnFlicTransparencyOn();
    NetPlayCheckboxOff2(pIndex);
    TurnFlicTransparencyOff();
    LetFlicFuckWithPalettes();
}

// IDA: void __usercall NetPlayRadioOn2(int pIndex@<EAX>, int pValue@<EDX>)
void NetPlayRadioOn2(int pIndex, int pValue) {
    LOG_TRACE("(%d, %d)", pIndex, pValue);

    RunFlicAt(288,
        gRadio_bastards__newgame[pIndex].left[pValue],
        gRadio_bastards__newgame[pIndex].top + 1);
}

// IDA: void __usercall NetPlayRadioOff2(int pIndex@<EAX>, int pValue@<EDX>)
void NetPlayRadioOff2(int pIndex, int pValue) {
    LOG_TRACE("(%d, %d)", pIndex, pValue);

    RunFlicAt(287,
        gRadio_bastards__newgame[pIndex].left[pValue],
        gRadio_bastards__newgame[pIndex].top + 1);
}

// IDA: void __usercall NetPlayRadioOn(int pIndex@<EAX>, int pValue@<EDX>)
void NetPlayRadioOn(int pIndex, int pValue) {
    LOG_TRACE("(%d, %d)", pIndex, pValue);

    RemoveTransientBitmaps(1);
    DontLetFlicFuckWithPalettes();
    TurnFlicTransparencyOn();
    NetPlayRadioOn2(pIndex, pValue);
    TurnFlicTransparencyOff();
    LetFlicFuckWithPalettes();
}

// IDA: void __usercall NetPlayRadioOff(int pIndex@<EAX>, int pValue@<EDX>)
void NetPlayRadioOff(int pIndex, int pValue) {
    LOG_TRACE("(%d, %d)", pIndex, pValue);

    RemoveTransientBitmaps(1);
    DontLetFlicFuckWithPalettes();
    TurnFlicTransparencyOn();
    NetPlayRadioOff2(pIndex, pValue);
    TurnFlicTransparencyOff();
    LetFlicFuckWithPalettes();
}

// IDA: void __cdecl DrawNOptInitialRadios()
void DrawNOptInitialRadios(void) {
    int i;
    int j;
    LOG_TRACE("()");

    RemoveTransientBitmaps(1);
    DontLetFlicFuckWithPalettes();
    TurnFlicTransparencyOn();
    for (i = 0; i < COUNT_OF(gRadio_bastards__newgame); i++) {
        if (gRadio_bastards__newgame[i].count < 2) {
            if (gRadio_bastards__newgame[i].current_value) {
                NetPlayCheckboxOn2(i);
            } else {
                NetPlayCheckboxOff2(i);
            }
        } else {
            NetPlayRadioOn2(i, gRadio_bastards__newgame[i].current_value);
            for (j = 0; j < gRadio_bastards__newgame[i].count; j++) {
                if (j != gRadio_bastards__newgame[i].current_value) {
                    NetPlayRadioOff2(i, j);
                }
            }
        }
    }
    TurnFlicTransparencyOff();
    LetFlicFuckWithPalettes();
}

// IDA: void __usercall NetRadioChanged(int pIndex@<EAX>, int pNew_value@<EDX>)
void NetRadioChanged(int pIndex, int pNew_value) {
    LOG_TRACE("(%d, %d)", pIndex, pNew_value);

    NetPlayRadioOff(pIndex, gRadio_bastards__newgame[pIndex].current_value);
    NetPlayRadioOn(pIndex, pNew_value);
    gRadio_bastards__newgame[pIndex].current_value = pNew_value;
}

// IDA: void __usercall NetCheckboxChanged(int pIndex@<EAX>)
void NetCheckboxChanged(int pIndex) {
    LOG_TRACE("(%d)", pIndex);

    if (gRadio_bastards__newgame[pIndex].current_value) {
        NetPlayCheckboxOff(pIndex);
    } else {
        NetPlayCheckboxOn(pIndex);
    }
    gRadio_bastards__newgame[pIndex].current_value = !gRadio_bastards__newgame[pIndex].current_value;
}

// IDA: int __usercall NetOptLeft@<EAX>(int *pCurrent_choice@<EAX>, int *pCurrent_mode@<EDX>)
int NetOptLeft(int* pCurrent_choice, int* pCurrent_mode) {
    int new_value;
    LOG_TRACE("(%p, %p)", pCurrent_choice, pCurrent_mode);

    DRS3StartSound(gEffects_outlet, 3000);
    if (gRadio_bastards__newgame[*pCurrent_choice - 3].count < 2) {
        NetCheckboxChanged(*pCurrent_choice - 3);
    } else {
        new_value = gRadio_bastards__newgame[*pCurrent_choice - 3].current_value - 1;
        if (new_value < 0) {
            new_value = gRadio_bastards__newgame[*pCurrent_choice - 3].count - 1;
        }
        NetRadioChanged(*pCurrent_choice - 3, new_value);
    }
    return 1;
}

// IDA: int __usercall NetOptRight@<EAX>(int *pCurrent_choice@<EAX>, int *pCurrent_mode@<EDX>)
int NetOptRight(int* pCurrent_choice, int* pCurrent_mode) {
    int new_value;
    LOG_TRACE("(%p, %p)", pCurrent_choice, pCurrent_mode);

    DRS3StartSound(gEffects_outlet, 3000);
    if (gRadio_bastards__newgame[*pCurrent_choice - 3].count < 2) {
        NetCheckboxChanged(*pCurrent_choice - 3);
    } else {
        new_value = gRadio_bastards__newgame[*pCurrent_choice - 3].current_value + 1;
        if (new_value == gRadio_bastards__newgame[*pCurrent_choice - 3].count) {
            new_value = 0;
        }
        NetRadioChanged(*pCurrent_choice - 3, new_value);
    }
    return 1;
}

// IDA: int __usercall NetOptUp@<EAX>(int *pCurrent_choice@<EAX>, int *pCurrent_mode@<EDX>)
int NetOptUp(int* pCurrent_choice, int* pCurrent_mode) {
    LOG_TRACE("(%p, %p)", pCurrent_choice, pCurrent_mode);

    if (*pCurrent_mode == 0) {
        *pCurrent_mode = 1;
        *pCurrent_choice = 13;
        DRS3StartSound(gEffects_outlet, 3000);
        return 1;
    } else if (*pCurrent_choice == 2) {
        *pCurrent_mode = 0;
        *pCurrent_choice = 0;
        DRS3StartSound(gEffects_outlet, 3000);
        return 1;
    } else {
        return 0;
    }
}

// IDA: int __usercall NetOptDown@<EAX>(int *pCurrent_choice@<EAX>, int *pCurrent_mode@<EDX>)
int NetOptDown(int* pCurrent_choice, int* pCurrent_mode) {
    LOG_TRACE("(%p, %p)", pCurrent_choice, pCurrent_mode);

    if (*pCurrent_mode == 0) {
        *pCurrent_mode = 1;
        *pCurrent_choice = 3;
        DRS3StartSound(gEffects_outlet, 3000);
        return 1;
    } else if (*pCurrent_choice == 14) {
        *pCurrent_mode = 0;
        *pCurrent_choice = 0;
        DRS3StartSound(gEffects_outlet, 3000);
        return 1;
    } else {
        return 0;
    }
}

// IDA: int __usercall NetRadioClick@<EAX>(int *pCurrent_choice@<EAX>, int *pCurrent_mode@<EDX>, int pX_offset@<EBX>, int pY_offset@<ECX>)
int NetRadioClick(int* pCurrent_choice, int* pCurrent_mode, int pX_offset, int pY_offset) {
    int i;
    LOG_TRACE("(%p, %p, %d, %d)", pCurrent_choice, pCurrent_mode, pX_offset, pY_offset);

    if (gRadio_bastards__newgame[*pCurrent_choice - 3].count < 2) {
        NetCheckboxChanged(*pCurrent_choice - 3);
    } else {
        for (i = gRadio_bastards__newgame[*pCurrent_choice - 3].count - 1; i >= 0; i--) {
            if (gThe_interface_spec__newgame->mouse_areas[3].left[gGraf_data_index] + pX_offset + 3 >= gRadio_bastards__newgame[*pCurrent_choice - 3].left[i]) {
                DRS3StartSound(gEffects_outlet, 3000);
                NetRadioChanged(*pCurrent_choice - 3, i);
                break;
            }
        }
    }
    return 0;
}

// IDA: void __cdecl RevertToDefaults()
void RevertToDefaults(void) {
    tPath_name the_path;
    FILE* f;
    tNet_game_options net_options;
    int i;
    LOG_TRACE("()");

    PathCat(the_path, gApplication_path, "NETDEFLT.TXT");
    f = DRfopen(the_path, "rt");
    if (f == NULL) {
        return;
    }
    for (i = 0; i < gLast_game_type + 1; i++) {
        ReadNetworkSettings(f, &net_options);
    }
    SetNetOptions(&net_options);
    DrawNOptInitialRadios();
    fclose(f);
}

// IDA: void __cdecl DefaultNetSettings()
void DefaultNetSettings(void) {
    FILE* f;
    int i;
    tPath_name the_path; // added

    PathCat(the_path, gApplication_path, "NETDEFLT.TXT");
    f = DRfopen(the_path, "rt");
    if (f == NULL) {
        return;
    }
    ReadNetworkSettings(f, gNet_settings);
    rewind(f);
    for (i = 0; i < COUNT_OF(gNet_settings) - 1; i++) {
        ReadNetworkSettings(f, &gNet_settings[i + 1]);
    }
    fclose(f);
}

// IDA: int __usercall NetOptGoAhead@<EAX>(int *pCurrent_choice@<EAX>, int *pCurrent_mode@<EDX>)
int NetOptGoAhead(int* pCurrent_choice, int* pCurrent_mode) {
    LOG_TRACE("(%p, %p)", pCurrent_choice, pCurrent_mode);

    if (*pCurrent_mode == 0) {
        if (*pCurrent_choice == 2) {
            RevertToDefaults();
            return 0;
        } else {
            return 1;
        }
    } else {
        NetOptRight(pCurrent_choice, pCurrent_mode);
        return 0;
    }
}

// IDA: void __usercall NetPlotAGraphBox(int pIndex@<EAX>, int pColour_value@<EDX>)
void NetPlotAGraphBox(int pIndex, int pColour_value) {
    LOG_TRACE("(%d, %d)", pIndex, pColour_value);

    if (pIndex >= 0) {
        DrawRRectangle(gBack_screen,
            gThe_interface_spec__newgame->mouse_areas[pIndex + 3].left[gGraf_data_index],
            gThe_interface_spec__newgame->mouse_areas[pIndex + 3].top[gGraf_data_index],
            gThe_interface_spec__newgame->mouse_areas[pIndex + 3].right[gGraf_data_index],
            gThe_interface_spec__newgame->mouse_areas[pIndex + 3].bottom[gGraf_data_index],
            pColour_value);
    }
}

// IDA: void __usercall NetDrawAGraphBox(int pIndex@<EAX>)
void NetDrawAGraphBox(int pIndex) {
    LOG_TRACE("(%d)", pIndex);

    NetPlotAGraphBox(pIndex, 45);
}

// IDA: void __usercall NetEraseAGraphBox(int pIndex@<EAX>)
void NetEraseAGraphBox(int pIndex) {
    LOG_TRACE("(%d)", pIndex);

    NetPlotAGraphBox(pIndex, 0);
}

// IDA: void __usercall DrawNetOptBox(int pCurrent_choice@<EAX>, int pCurrent_mode@<EDX>)
void DrawNetOptBox(int pCurrent_choice, int pCurrent_mode) {
    LOG_TRACE("(%d, %d)", pCurrent_choice, pCurrent_mode);

    if (gRadio_selected != pCurrent_choice) {
        NetEraseAGraphBox(gRadio_selected - 3);
        NetDrawAGraphBox(pCurrent_choice - 3);
        gRadio_selected = pCurrent_choice;
    }
}

// IDA: void __usercall DoNetOptions(tNet_game_options *pGame_options@<EAX>)
void DoNetOptions(tNet_game_options* pGame_options) {
    static tFlicette flicker_on[14] = {
        { 43, { 169, 90 }, { 156, 398 } },
        { 43, { 236, 440 }, { 156, 398 } },
        { 43, { 37, 440 }, { 156, 398 } },
        { 68, { 81, 98 }, { 32, 86 } },
        { 75, { 81, 98 }, { 43, 108 } },
        { 77, { 81, 98 }, { 54, 130 } },
        { 79, { 81, 98 }, { 65, 161 } },
        { 112, { 81, 98 }, { 76, 182 } },
        { 127, { 81, 98 }, { 86, 214 } },
        { 129, { 81, 98 }, { 97, 235 } },
        { 134, { 81, 98 }, { 108, 266 } },
        { 137, { 81, 98 }, { 119, 288 } },
        { 139, { 81, 98 }, { 130, 310 } },
        { 143, { 81, 98 }, { 141, 331 } },
    };
    static tFlicette flicker_off[14] = {
        { 42, { 169, 90 }, { 156, 398 } },
        { 42, { 236, 440 }, { 156, 398 } },
        { 42, { 37, 440 }, { 156, 398 } },
        { 69, { 81, 98 }, { 32, 86 } },
        { 76, { 81, 98 }, { 43, 108 } },
        { 78, { 81, 98 }, { 54, 130 } },
        { 109, { 81, 98 }, { 65, 161 } },
        { 113, { 81, 98 }, { 76, 182 } },
        { 128, { 81, 98 }, { 86, 214 } },
        { 133, { 81, 98 }, { 97, 235 } },
        { 136, { 81, 98 }, { 108, 266 } },
        { 138, { 81, 98 }, { 119, 288 } },
        { 142, { 81, 98 }, { 130, 310 } },
        { 148, { 81, 98 }, { 141, 331 } },
    };
    static tFlicette push[14] = {
        { 154, { 169, 90 }, { 156, 398 } },
        { 45, { 236, 440 }, { 156, 398 } },
        { 67, { 37, 440 }, { 156, 398 } },
        { 68, { 81, 98 }, { 32, 86 } },
        { 75, { 81, 98 }, { 43, 108 } },
        { 77, { 81, 98 }, { 54, 130 } },
        { 79, { 81, 98 }, { 65, 161 } },
        { 112, { 81, 98 }, { 76, 182 } },
        { 127, { 81, 98 }, { 86, 214 } },
        { 129, { 81, 98 }, { 97, 235 } },
        { 134, { 81, 98 }, { 108, 266 } },
        { 137, { 81, 98 }, { 119, 288 } },
        { 139, { 81, 98 }, { 130, 310 } },
        { 143, { 81, 98 }, { 141, 331 } },
    };
    static tMouse_area mouse_areas[14] = {
        { { 169, 90 }, { 156, 396 }, { 232, 214 }, { 176, 444 }, 0, 0, 0, NULL },
        { { 236, 440 }, { 156, 396 }, { 299, 552 }, { 176, 444 }, 1, 0, 0, NULL },
        { { 37, 98 }, { 156, 84 }, { 100, 568 }, { 176, 103 }, 2, 0, 0, NULL },
        { { 61, 98 }, { 30, 106 }, { 270, 568 }, { 40, 125 }, 3, 1, 0, NetRadioClick },
        { { 61, 98 }, { 41, 127 }, { 270, 568 }, { 51, 146 }, 4, 1, 0, NetRadioClick },
        { { 61, 98 }, { 52, 158 }, { 270, 568 }, { 62, 178 }, 5, 1, 0, NetRadioClick },
        { { 61, 98 }, { 63, 180 }, { 270, 568 }, { 73, 199 }, 6, 1, 0, NetRadioClick },
        { { 61, 98 }, { 74, 192 }, { 270, 568 }, { 84, 230 }, 7, 1, 0, NetRadioClick },
        { { 61, 98 }, { 84, 233 }, { 270, 568 }, { 94, 252 }, 8, 1, 0, NetRadioClick },
        { { 61, 98 }, { 95, 264 }, { 270, 568 }, { 105, 283 }, 9, 1, 0, NetRadioClick },
        { { 61, 98 }, { 106, 286 }, { 270, 568 }, { 116, 305 }, 10, 1, 0, NetRadioClick },
        { { 61, 98 }, { 117, 307 }, { 270, 322 }, { 127, 326 }, 11, 1, 0, NetRadioClick },
        { { 61, 98 }, { 128, 329 }, { 270, 322 }, { 138, 348 }, 12, 1, 0, NetRadioClick },
        { { 61, 98 }, { 139, 358 }, { 270, 322 }, { 149, 377 }, 13, 1, 0, NetRadioClick },
    };
    static tInterface_spec interface_spec = {
        0, 65, 0, 66, 66, 66, -1,
        { -1, 0 }, { -1, 0 }, { 0, 3 }, { 2, 13 }, { NULL, NetOptLeft },
        { -1, 0 }, { 1, 0 }, { 0, 3 }, { 2, 13 }, { NULL, NetOptRight },
        { -1, -1 }, { 0, -1 }, { 0, 2 }, { 0, 13 }, { NetOptUp, NetOptUp },
        { -1, -1 }, { 1, 1 }, { 0, 3 }, { 0, 14 }, { NetOptDown, NetOptDown },
        { 1, 1 }, { NetOptGoAhead, NetOptGoAhead }, { 1, 1 }, { NULL, NULL },
        NULL, DrawNetOptBox, 0,
        NULL, DrawNOptInitialRadios, NULL, 0, { 0, 0 }, NULL, 1, 1,
        COUNT_OF(flicker_on), flicker_on, flicker_off, push,
        COUNT_OF(mouse_areas), mouse_areas,
        0, NULL
    };
    LOG_TRACE("(%p)", pGame_options);

    gThe_interface_spec__newgame = &interface_spec;
    gRadio_selected = -1;
    LoadFont(kFont_GRYLIT);
    SetNetOptions(pGame_options);
    if (DoInterfaceScreen(&interface_spec, 0, 0) == 0) {
        GetNetOptions(pGame_options);
    }
    DisposeFont(12);
}

// IDA: void __usercall PlayRadioOn(int pIndex@<EAX>)
//  Suffix added to avoid duplicate symbol
void PlayRadioOn__newgame(int pIndex) {
    LOG_TRACE("(%d)", pIndex);

    RunFlicAt(288,
        gThe_interface_spec__newgame->pushed_flics[pIndex + 4].x[gGraf_data_index],
        gThe_interface_spec__newgame->pushed_flics[pIndex + 4].y[gGraf_data_index]);
}

// IDA: void __usercall PlayRadioOff(int pIndex@<EAX>)
//  Suffix added to avoid duplicate symbol
void PlayRadioOff__newgame(int pIndex) {
    LOG_TRACE("(%d)", pIndex);

    RunFlicAt(287,
        gThe_interface_spec__newgame->pushed_flics[pIndex + 4].x[gGraf_data_index],
        gThe_interface_spec__newgame->pushed_flics[pIndex + 4].y[gGraf_data_index]);
}

// IDA: void __usercall SetOptions(tNet_game_type pGame_type@<EAX>, tNet_game_options *pGame_options@<EDX>)
void SetOptions(tNet_game_type pGame_type, tNet_game_options* pGame_options) {
    LOG_TRACE("(%d, %p)", pGame_type, pGame_options);

    pGame_options->show_players_on_map = gNet_settings[0].show_players_on_map;
    pGame_options->show_peds_on_map = gNet_settings[pGame_type + 1].show_peds_on_map;
    pGame_options->enable_text_messages = gNet_settings[0].enable_text_messages;
    pGame_options->powerup_respawn = gNet_settings[0].powerup_respawn;
    pGame_options->show_powerups_on_map = gNet_settings[0].show_powerups_on_map;
    pGame_options->open_game = gNet_settings[pGame_type + 1].open_game;
    pGame_options->grid_start = gNet_settings[pGame_type + 1].grid_start;
    pGame_options->random_car_choice = gNet_settings[0].random_car_choice;
    pGame_options->car_choice = gNet_settings[0].car_choice;
    pGame_options->starting_money_index = gNet_settings[0].starting_money_index;
    pGame_options->race_sequence_type = gNet_settings[0].race_sequence_type;
}

// IDA: void __cdecl DrawNetChooseInitial()
void DrawNetChooseInitial(void) {
    LOG_TRACE("()");

    PlayRadioOn__newgame(gLast_game_type);
}

// IDA: int __usercall NetChooseGoAhead@<EAX>(int *pCurrent_choice@<EAX>, int *pCurrent_mode@<EDX>)
int NetChooseGoAhead(int* pCurrent_choice, int* pCurrent_mode) {
    LOG_TRACE("(%p, %p)", pCurrent_choice, pCurrent_mode);

    if (*pCurrent_mode == 0) {
        return 1;
    } else {
        if (*pCurrent_choice - 4 != gLast_game_type) {
            RemoveTransientBitmaps(1);
            DontLetFlicFuckWithPalettes();
            TurnFlicTransparencyOn();
            PlayRadioOff__newgame(gLast_game_type);
            gLast_game_type = *pCurrent_choice - 4;
            PlayRadioOn__newgame(gLast_game_type);
            LoadRaces(gRace_list, &gNumber_of_races, gLast_game_type);
            SetOptions(gLast_game_type, gOptions);
            TurnFlicTransparencyOff();
            LetFlicFuckWithPalettes();
            if (gRace_index >= gNumber_of_races) {
                gRace_index = PickNetRace(-1, gNet_race_sequence__newgame);
            }
            if (!gMouse_in_use) {
                *pCurrent_mode = 0;
                *pCurrent_choice = 0;
            }
        }
        return 0;
    }
}

// IDA: void __usercall PlotAGraphBox(int pIndex@<EAX>, int pColour_value@<EDX>)
// Suffix added to avoid duplicate symbol
void PlotAGraphBox__newgame(int pIndex, int pColour_value) {
    LOG_TRACE("(%d, %d)", pIndex, pColour_value);

    if (pIndex >= 0) {
        DrawRRectangle(gBack_screen,
            gThe_interface_spec__newgame->mouse_areas[pIndex].left[gGraf_data_index],
            gThe_interface_spec__newgame->mouse_areas[pIndex].top[gGraf_data_index],
            gThe_interface_spec__newgame->mouse_areas[pIndex].right[gGraf_data_index],
            gThe_interface_spec__newgame->mouse_areas[pIndex].bottom[gGraf_data_index],
            pColour_value);
    }
}

// IDA: void __usercall DrawAGraphBox(int pIndex@<EAX>)
// Suffix added to avoid duplicate symbol
void DrawAGraphBox__newgame(int pIndex) {
    LOG_TRACE("(%d)", pIndex);

    PlotAGraphBox__newgame(pIndex, 45);
}

// IDA: void __usercall EraseAGraphBox(int pIndex@<EAX>)
// Suffix added to avoid duplicate symbol
void EraseAGraphBox__newgame(int pIndex) {
    LOG_TRACE("(%d)", pIndex);

    PlotAGraphBox__newgame(pIndex, 0);
}

// IDA: void __usercall DrawNetChoose(int pCurrent_choice@<EAX>, int pCurrent_mode@<EDX>)
void DrawNetChoose(int pCurrent_choice, int pCurrent_mode) {
    char s[256];
    tU32* k;
    int i;
    LOG_TRACE("(%d, %d)", pCurrent_choice, pCurrent_mode);

    if (gLast_net_choose_box >= 0) {
        EraseAGraphBox__newgame(gLast_net_choose_box);
    }
    if (pCurrent_mode != 0) {
        gLast_net_choose_box = pCurrent_choice;
        DrawAGraphBox__newgame(pCurrent_choice);
    } else {
        gLast_net_choose_box = -1;
    }
    BrPixelmapRectangleFill(gBack_screen,
        gCurrent_graf_data->net_descr_race_l,
        gCurrent_graf_data->net_choose_race_y,
        gCurrent_graf_data->net_descr_race_r - gCurrent_graf_data->net_descr_race_l,
        gFonts[kFont_GRNLIT].height + 1 - (TranslationMode() ? 2 : 0), 0);
    sprintf(s, "%s %s", GetMiscString(kMiscString_FIRST_RACE), gRace_list[gRace_index].name);
    DRPixelmapCentredText(gBack_screen,
        gCurrent_graf_data->net_choose_race_x,
        gCurrent_graf_data->net_choose_race_y,
        &gFonts[kFont_GRNLIT], s);
    BrPixelmapRectangleFill(gBack_screen,
        gCurrent_graf_data->net_descr_race_l,
        gCurrent_graf_data->net_descr_race_top - (TranslationMode() ? 2 : 0),
        gCurrent_graf_data->net_descr_race_r - gCurrent_graf_data->net_descr_race_l,
        gCurrent_graf_data->net_descr_race_bot - gCurrent_graf_data->net_descr_race_top,
        0);
    OoerrIveGotTextInMeBoxMissus(9,
        GetMiscString(kMiscString_NetworkGameTypeDescriptions_START + (pCurrent_mode ? pCurrent_choice - 4 : gLast_game_type)),
        gBack_screen,
        gCurrent_graf_data->net_descr_race_l,
        gCurrent_graf_data->net_descr_race_top,
        gCurrent_graf_data->net_descr_race_r,
        gCurrent_graf_data->net_descr_race_bot,
        1);
    k = KevKeyService();
    if (k[0] == 0x2212d981 && k[1] == 0x90e8cf51) {
        DRS3StartSound(gEffects_outlet, 3202);
        DRS3StartSound(gEffects_outlet, 3202);
        for (i = 0; i < gNumber_of_racers; i++) {
            if (gOpponents[i].network_availability == eNet_avail_never) {
                gOpponents[i].network_availability = eNet_avail_all;
            }
        }
    }
}

// IDA: int __usercall NetChooseLR@<EAX>(int *pCurrent_choice@<EAX>, int *pCurrent_mode@<EDX>)
int NetChooseLR(int* pCurrent_choice, int* pCurrent_mode) {
    LOG_TRACE("(%p, %p)", pCurrent_choice, pCurrent_mode);

    *pCurrent_choice = gLast_game_type + 4;
    return 0;
}

// IDA: void __usercall SetGameTarget(tNet_game_type *pGame_type@<EAX>, tNet_game_options *pGame_options@<EDX>)
void SetGameTarget(tNet_game_type* pGame_type, tNet_game_options* pGame_options) {
    LOG_TRACE("(%p, %p)", pGame_type, pGame_options);

    pGame_options->race_end_target = gNet_target[*pGame_type];
    switch (*pGame_type) {
    case eNet_game_type_car_crusher:
        pGame_options->race_end_target = gNet_target[*pGame_type];
        break;
    case eNet_game_type_tag:
        pGame_options->race_end_target = 1000 * gNet_target[*pGame_type];
        break;
    case eNet_game_type_foxy:
        pGame_options->race_end_target = 1000 * gNet_target[*pGame_type];
        break;
    default:
        pGame_options->race_end_target = gNet_target[*pGame_type];
        break;
    }
}

// IDA: int __usercall NetGameChoices@<EAX>(tNet_game_type *pGame_type@<EAX>, tNet_game_options *pGame_options@<EDX>, int *pRace_index@<EBX>)
int NetGameChoices(tNet_game_type* pGame_type, tNet_game_options* pGame_options, int* pRace_index) {
    static tFlicette flicker_on[11] = {
        { 43, { 226, 90 }, { 117, 398 } },
        { 43, { 226, 440 }, { 148, 398 } },
        { 43, { 226, 98 }, { 42, 86 } },
        { 43, { 226, 98 }, { 74, 108 } },
        { 162, { 74, 98 }, { 73, 130 } },
        { 163, { 74, 98 }, { 83, 161 } },
        { 164, { 74, 98 }, { 93, 182 } },
        { 165, { 74, 98 }, { 103, 214 } },
        { 166, { 74, 98 }, { 113, 235 } },
        { 167, { 74, 98 }, { 123, 266 } },
        { 168, { 74, 98 }, { 133, 288 } },
    };
    static tFlicette flicker_off[11] = {
        { 42, { 226, 90 }, { 117, 398 } },
        { 42, { 226, 440 }, { 148, 398 } },
        { 42, { 226, 98 }, { 42, 86 } },
        { 42, { 226, 98 }, { 74, 108 } },
        { 182, { 74, 98 }, { 73, 130 } },
        { 183, { 74, 98 }, { 83, 161 } },
        { 184, { 74, 98 }, { 93, 182 } },
        { 185, { 74, 98 }, { 103, 214 } },
        { 186, { 74, 98 }, { 113, 235 } },
        { 187, { 74, 98 }, { 123, 266 } },
        { 188, { 74, 98 }, { 133, 288 } },
    };
    static tFlicette push[11] = {
        { 88, { 227, 90 }, { 117, 398 } },
        { 45, { 226, 440 }, { 148, 398 } },
        { 195, { 227, 98 }, { 42, 86 } },
        { 124, { 226, 98 }, { 74, 108 } },
        { 162, { 74, 98 }, { 73, 130 } },
        { 163, { 74, 98 }, { 83, 161 } },
        { 164, { 74, 98 }, { 93, 182 } },
        { 165, { 74, 98 }, { 103, 214 } },
        { 166, { 74, 98 }, { 113, 235 } },
        { 167, { 74, 98 }, { 123, 266 } },
        { 168, { 74, 98 }, { 133, 288 } },
    };
    static tMouse_area mouse_areas[11] = {
        { { 226, 90 }, { 117, 396 }, { 290, 214 }, { 137, 444 }, 0, 0, 0, NULL },
        { { 226, 440 }, { 148, 396 }, { 290, 552 }, { 168, 444 }, 1, 0, 0, NULL },
        { { 226, 98 }, { 42, 84 }, { 290, 568 }, { 62, 103 }, 2, 0, 0, NULL },
        { { 226, 98 }, { 74, 106 }, { 290, 568 }, { 94, 125 }, 3, 0, 0, NULL },
        { { 53, 98 }, { 71, 127 }, { 211, 568 }, { 79, 146 }, 4, 1, 0, NULL },
        { { 53, 98 }, { 81, 158 }, { 211, 568 }, { 89, 178 }, 5, 1, 0, NULL },
        { { 53, 98 }, { 91, 180 }, { 211, 568 }, { 99, 199 }, 6, 1, 0, NULL },
        { { 53, 98 }, { 101, 192 }, { 211, 568 }, { 109, 230 }, 7, 1, 0, NULL },
        { { 53, 98 }, { 111, 233 }, { 211, 568 }, { 119, 252 }, 8, 1, 0, NULL },
        { { 53, 98 }, { 121, 264 }, { 211, 568 }, { 129, 283 }, 9, 1, 0, NULL },
        { { 53, 98 }, { 131, 264 }, { 211, 568 }, { 139, 283 }, 10, 1, 0, NULL },
    };
    static tInterface_spec interface_spec = {
        0, 122, 0, 0, 0, 0, -1,
        { 1, 0 }, { 4, -10 }, { 4, 0 }, { 4, 0 }, { NetChooseLR, NULL },
        { 1, 0 }, { 3, -10 }, { 4, 0 }, { 4, 0 }, { NetChooseLR, NULL },
        { -1, -1 }, { -1, -1 }, { 0, 4 }, { 3, 10 }, { NULL, NULL },
        { -1, -1 }, { 1, 1 }, { 0, 4 }, { 3, 10 }, { NULL, NULL },
        { 1, 1 }, { NetChooseGoAhead, NetChooseGoAhead }, { 1, 1 }, { NULL, NULL },
        NULL, DrawNetChoose, 0, NULL, DrawNetChooseInitial, NULL, 0, { 0, 0 }, NULL,
        1, 1,
        COUNT_OF(flicker_on), flicker_on, flicker_off, push,
        COUNT_OF(mouse_areas), mouse_areas,
        0, NULL

    };
    int result;
    LOG_TRACE("(%p, %p, %p)", pGame_type, pGame_options, pRace_index);

    gRace_index = *pRace_index;
    gLast_game_type = *pGame_type;
    do {
        LoadFont(kFont_GRNLIT);
        LoadFont(kFont_GRNDK);
        gThe_interface_spec__newgame = &interface_spec;
        gLast_net_choose_box = -1;
        gOptions = pGame_options;
        gNet_race_sequence__newgame = pGame_options->race_sequence_type;
        result = DoInterfaceScreen(&interface_spec, 0, 0);
        if (result || !pGame_options->random_car_choice) {
            RunFlic(123);
        } else {
            FadePaletteDown();
        }
        DisposeFont(9);
        DisposeFont(10);
        switch (result) {
        case 0:
            *pGame_type = gLast_game_type;
            *pRace_index = gRace_index;
            break;
        case 2:
            ChangeRace(&gRace_index, 1, pGame_options->race_sequence_type);
            break;
        case 3:
            DoNetOptions(pGame_options);
            break;
        }
    } while (result > 1);
    gLast_game_type = *pGame_type;
    gNet_settings[0] = *pGame_options;
    gNet_settings[gLast_game_type + 1] = *pGame_options;
    SaveOptions();
    SetGameTarget(pGame_type, pGame_options);
    return result == 0;
}

// IDA: void __usercall ReadNetGameChoices(tNet_game_type *pGame_type@<EAX>, tNet_game_options *pGame_options@<EDX>, int *pRace_index@<EBX>)
void ReadNetGameChoices(tNet_game_type* pGame_type, tNet_game_options* pGame_options, int* pRace_index) {
    LOG_TRACE("(%p, %p, %p)", pGame_type, pGame_options, pRace_index);

    *pGame_type = gLast_game_type;
    SetOptions(*pGame_type, pGame_options);
    SetGameTarget(pGame_type, pGame_options);
    *pRace_index = PickNetRace(-1, pGame_options->race_sequence_type);
}

// IDA: int __usercall ChooseStartRace@<EAX>(int *pRank@<EAX>)
int ChooseStartRace(int* pRank) {
    LOG_TRACE("(%p)", pRank);

    *pRank = 0;
    return 1;
}

// IDA: void __usercall SetUpOtherNetThings(tNet_game_details *pNet_game@<EAX>)
void SetUpOtherNetThings(tNet_game_details* pNet_game) {
    LOG_TRACE("(%p)", pNet_game);

    StartLoadingScreen();
    gProgram_state.skill_level = 1;
    InitGame(pNet_game->start_race);
}

// IDA: void __usercall RequestCarDetails(tNet_game_details *pNet_game@<EAX>)
void RequestCarDetails(tNet_game_details* pNet_game) {
    tNet_message* message;
    LOG_TRACE("(%p)", pNet_game);

    gReceived_car_details = 0;
    if (gNet_mode == eNet_mode_none) {
        gNet_mode = eNet_mode_thinking_about_it;
    }
    message = NetBuildMessage(NETMSGID_CARDETAILSREQ, 0);
    NetGuaranteedSendMessageToAddress(pNet_game, message, &pNet_game->pd_net_info.addr_in, NULL);
}

// IDA: int __cdecl PickARandomCar()
int PickARandomCar(void) {
    int i;
    int cars[120];
    int array_size;
    LOG_TRACE("()");

    array_size = 0;
    for (i = 0; i < gNumber_of_racers; i++) {
        if (gCar_details[i].ownership == eCar_owner_none) {
            cars[array_size] = i;
            array_size++;
            if (gOpponents[i].network_availability == eNet_avail_all) {
                cars[array_size] = i;
                array_size++;
            }
        }
    }
    return cars[IRandomBetween(0, array_size - 1)];
}

// IDA: void __usercall PollCarDetails(tNet_game_details *pNet_game@<EAX>)
void PollCarDetails(tNet_game_details* pNet_game) {
    LOG_TRACE("(%p)", pNet_game);

    if (gReceived_car_details) {
        RequestCarDetails(pNet_game);
    }
}

// IDA: void __usercall SetNetAvailability(tNet_game_options *pOptions@<EAX>)
void SetNetAvailability(tNet_game_options* pOptions) {
    int i;
    LOG_TRACE("(%p)", pOptions);

    for (i = 0; i < gNumber_of_racers; i++) {
        switch (gOpponents[i].network_availability) {
        case eNet_avail_never:
            gCar_details[i].ownership = eCar_owner_not_allowed;
            break;
        case eNet_avail_eagle:
            if (pOptions->car_choice == eNet_car_annie) {
                gCar_details[i].ownership = eCar_owner_not_allowed;
            } else {
                gCar_details[i].ownership = eCar_owner_none;
            }
            break;
        case eNet_avail_hawk:
            if (pOptions->car_choice == eNet_car_frankie) {
                gCar_details[i].ownership = eCar_owner_not_allowed;
            } else {
                gCar_details[i].ownership = eCar_owner_none;
            }
            break;
        case eNet_avail_all:
            if (pOptions->car_choice == eNet_car_all) {
                gCar_details[i].ownership = eCar_owner_none;
            } else {
                gCar_details[i].ownership = eCar_owner_not_allowed;
            }
        }
    }
}

// IDA: int __usercall ChooseNetCar@<EAX>(tNet_game_details *pNet_game@<EAX>, tNet_game_options *pOptions@<EDX>, int *pCar_index@<EBX>, int pIm_the_host_so_fuck_off@<ECX>)
int ChooseNetCar(tNet_game_details* pNet_game, tNet_game_options* pOptions, int* pCar_index, int pIm_the_host_so_fuck_off) {
    tS32 start_time;
    int i;
    int result;
    int car_index;
    int the_car_index;
    LOG_TRACE("(%p, %p, %p, %d)", pNet_game, pOptions, pCar_index, pIm_the_host_so_fuck_off);

    if (!pOptions->random_car_choice || pIm_the_host_so_fuck_off) {
        gNet_options = pOptions;
        if (pIm_the_host_so_fuck_off) {
            SetNetAvailability(pOptions);
        } else {
            RequestCarDetails(pNet_game);
            start_time = PDGetTotalTime();
            while (!gReceived_car_details && PDGetTotalTime() - start_time < 10000) {
                NetService(0);
            }
            gNet_mode = eNet_mode_none;
            if (!gReceived_car_details) {
                gNet_mode = eNet_mode_none;
                return 0;
            }
        }
        if (pOptions->random_car_choice) {
            *pCar_index = PickARandomCar();
            if (pIm_the_host_so_fuck_off && *pCar_index >= 0) {
                gCar_details[*pCar_index].ownership = eCar_owner_someone;
            }
            result = 1;
        } else {
            if (*pCar_index < 0) {
                *pCar_index = PickARandomCar();
                car_index = 0;
                for (i = 0; i < gNumber_of_racers; i++) {
                    if (gCar_details[i].ownership < eCar_owner_not_allowed) {
                        gProgram_state.cars_available[car_index] = i;
                        car_index++;
                    }
                }
                gProgram_state.number_of_cars = car_index;
            }
            result = ChangeCar(1, pCar_index, pNet_game);
            gNet_mode = eNet_mode_none;
            if (pIm_the_host_so_fuck_off) {
                gCar_details[*pCar_index].ownership = eCar_owner_someone;
            }
        }
    } else {
        result = 1;
    }
    return result;
}

// IDA: void __cdecl InitNetStorageSpace()
void InitNetStorageSpace(void) {
    int i;
    LOG_TRACE("()");

    for (i = 0; i < COUNT_OF(gNet_players); i++) {
        gCurrent_race.opponent_list[i].car_spec = NULL;
    }
    gNet_storage_allocated = 1;
    InitialiseStorageSpace(&gNet_cars_storage_space, 500, 50, 500, 300);
}

// IDA: void __cdecl DisposeNetStorageSpace()
void DisposeNetStorageSpace(void) {
    LOG_TRACE("()");

    if (gNet_storage_allocated) {
        DisposeStorageSpace(&gNet_cars_storage_space);
    }
    gNet_storage_allocated = 0;
}

// IDA: int __cdecl DoMultiPlayerStart()
int DoMultiPlayerStart(void) {
    tJoin_or_host_result result;
    tNet_game_details* game_to_join;
    char s[256];
    char* s2;
    tNet_game_type new_game_type;
    tNet_game_options new_game_options;
    int start_rank;
    int car_index;
    int race_index; // added by dethrace
    LOG_TRACE("()");

    if (harness_game_info.mode == eGame_carmageddon_demo || harness_game_info.mode == eGame_splatpack_demo || harness_game_info.mode == eGame_splatpack_xmas_demo) {
        SuspendPendingFlic();
        DoFeatureUnavailableInDemo();
        return 0;
    }

    if (gAusterity_mode) {
        NetFullScreenMessage(kMiscString_NOT_ENOUGH_MEMORY, 0);
        return 0;
    }
    if (NetInitialise()) {
        SuspendPendingFlic();
        DoErrorInterface(kMiscString_NetworkPlayIsNotAvailable);
        return 0;
    }
    gSynch_race_start = 0;
    gPending_race = -1;
    gCurrent_race.number_of_racers = 0;
    gAsk_time = 0;
    AboutToLoadFirstCar();
    result = JoinOrHostGame(&game_to_join);
    switch (result) {
    case eJoin_or_host_cancel:
        return 0;
    case eJoin_or_host_join:
        gProgram_state.frank_or_anniness = eFrankie;
        if (!CarmaCDinDriveOrFullGameInstalled()) {
            DoErrorInterface(kMiscString_PLEASE_INSERT_THE_CARMAGEDDON_CD);
            return 0;
        }
        car_index = -1;
        // Abusing 'start_rank' here, it's probably better to introduce a new variable name (e.g. join_result)
        start_rank = -4;
        while (start_rank == -4) {
            start_rank = 0;
            if (!ChooseNetCar(game_to_join, &game_to_join->options, &car_index, 0)) {
                continue;
            }
            StartLoadingScreen();
            InitNetStorageSpace();
            start_rank = NetJoinGame(game_to_join, gProgram_state.player_name[0], car_index);
            if (start_rank == 0) {
                LoadRaces(gRace_list, &gNumber_of_races, gCurrent_net_game->type);
                SetUpOtherNetThings(game_to_join);
                ReenableNetService();
                strcpy(s, gProgram_state.player_name[0]);
                strcat(s, " ");
                strcat(s, GetMiscString(kMiscString_HasJoinedTheGame));
                NetSendHeadupToAllPlayers(s);
                start_rank = 1;
            } else {
                DisposeNetStorageSpace();
            }
        }
        if (start_rank <= 0) {
            NetDisposeGameDetails(game_to_join);
            return 0;
        } else {
            return 1;
        }
    case eJoin_or_host_host:
#if defined(DETHRACE_FIX_BUGS)
        /* Don't allow hosting a game when the game is launched with --no-bind */
        if (harness_game_config.no_bind) {
            DoErrorInterface(76);
            return 0;
        }
#endif
        gProgram_state.frank_or_anniness = eFrankie;
        if (!OriginalCarmaCDinDrive()) {
            DoErrorInterface(kMiscString_PLEASE_INSERT_THE_CARMAGEDDON_CD);
            return 0;
        }
        car_index = -1;
        ReadNetGameChoices(&new_game_type, &new_game_options, &start_rank);
        LoadRaces(gRace_list, &gNumber_of_races, new_game_type);
        start_rank = PickNetRace(-1, new_game_options.race_sequence_type);
        if (NetGameChoices(&new_game_type, &new_game_options, &start_rank) != 0
            && ChooseNetCar(gCurrent_net_game, &new_game_options, &car_index, 1) != 0) {
            StartLoadingScreen();
            InitNetStorageSpace();
            if (NetHostGame(new_game_type, &new_game_options, start_rank, gProgram_state.player_name[0], car_index) != NULL) {
                SetUpOtherNetThings(gCurrent_net_game);
                ReenableNetService();
                return 1;
            } else {
                DisposeNetStorageSpace();
                ReenableNetService();
                NetLeaveGame(gCurrent_net_game);
                return 0;
            }
        }
    default:
        return 0;
    }
}
