mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2025-01-05 07:01:18 +00:00
Allowed enabling/disabling of mods through the UI
This commit is contained in:
parent
79a659781c
commit
fe11e25e0b
17 changed files with 356 additions and 202 deletions
|
@ -466,7 +466,9 @@
|
|||
<ClCompile Include="..\src\pc\djui\djui_panel_controls.c" />
|
||||
<ClCompile Include="..\src\pc\djui\djui_panel_host.c" />
|
||||
<ClCompile Include="..\src\pc\djui\djui_panel_host_message.c" />
|
||||
<ClCompile Include="..\src\pc\djui\djui_panel_host_mods.c" />
|
||||
<ClCompile Include="..\src\pc\djui\djui_panel_host_save.c" />
|
||||
<ClCompile Include="..\src\pc\djui\djui_panel_host_settings.c" />
|
||||
<ClCompile Include="..\src\pc\djui\djui_panel_join.c" />
|
||||
<ClCompile Include="..\src\pc\djui\djui_panel_join_message.c" />
|
||||
<ClCompile Include="..\src\pc\djui\djui_panel_menu.c" />
|
||||
|
@ -931,7 +933,9 @@
|
|||
<ClInclude Include="..\src\pc\djui\djui_panel_controls.h" />
|
||||
<ClInclude Include="..\src\pc\djui\djui_panel_host.h" />
|
||||
<ClInclude Include="..\src\pc\djui\djui_panel_host_message.h" />
|
||||
<ClInclude Include="..\src\pc\djui\djui_panel_host_mods.h" />
|
||||
<ClInclude Include="..\src\pc\djui\djui_panel_host_save.h" />
|
||||
<ClInclude Include="..\src\pc\djui\djui_panel_host_settings.h" />
|
||||
<ClInclude Include="..\src\pc\djui\djui_panel_join.h" />
|
||||
<ClInclude Include="..\src\pc\djui\djui_panel_join_message.h" />
|
||||
<ClInclude Include="..\src\pc\djui\djui_panel_menu.h" />
|
||||
|
|
|
@ -4854,6 +4854,12 @@
|
|||
<ClCompile Include="..\src\pc\djui\djui_progress_bar.c">
|
||||
<Filter>Source Files\src\pc\djui\component\compound</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\pc\djui\djui_panel_host_settings.c">
|
||||
<Filter>Source Files\src\pc\djui\panel</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\pc\djui\djui_panel_host_mods.c">
|
||||
<Filter>Source Files\src\pc\djui\panel</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\actors\common0.h">
|
||||
|
@ -5989,5 +5995,11 @@
|
|||
<ClInclude Include="..\src\pc\djui\djui_progress_bar.h">
|
||||
<Filter>Source Files\src\pc\djui\component\compound</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\pc\djui\djui_panel_host_settings.h">
|
||||
<Filter>Source Files\src\pc\djui\panel</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\pc\djui\djui_panel_host_mods.h">
|
||||
<Filter>Source Files\src\pc\djui\panel</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -13,6 +13,7 @@
|
|||
#include "gfx/gfx_window_manager_api.h"
|
||||
#include "controller/controller_api.h"
|
||||
#include "fs/fs.h"
|
||||
#include "pc/mod_list.h"
|
||||
|
||||
#define ARRAY_LEN(arr) (sizeof(arr) / sizeof(arr[0]))
|
||||
|
||||
|
@ -295,12 +296,25 @@ void configfile_load(const char *filename) {
|
|||
if (numTokens >= 2) {
|
||||
const struct ConfigOption *option = NULL;
|
||||
|
||||
// enable mods
|
||||
if (!strcmp(tokens[0], "enable-mod:")) {
|
||||
for (unsigned int i = 0; i < gModTableLocal.entryCount; i++) {
|
||||
struct ModListEntry* entry = &gModTableLocal.entries[i];
|
||||
if (!strcmp(tokens[1], entry->name)) {
|
||||
entry->enabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < ARRAY_LEN(options); i++) {
|
||||
if (strcmp(tokens[0], options[i].name) == 0) {
|
||||
option = &options[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (option == NULL)
|
||||
printf("unknown option '%s'\n", tokens[0]);
|
||||
else {
|
||||
|
@ -384,5 +398,12 @@ void configfile_save(const char *filename) {
|
|||
}
|
||||
}
|
||||
|
||||
// save enabled mods
|
||||
for (unsigned int i = 0; i < gModTableLocal.entryCount; i++) {
|
||||
struct ModListEntry* entry = &gModTableLocal.entries[i];
|
||||
if (!entry->enabled) { continue; }
|
||||
fprintf(file, "%s %s\n", "enable-mod:", entry->name);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
#include "djui_panel_debug.h"
|
||||
#include "djui_panel_main.h"
|
||||
#include "djui_panel_host.h"
|
||||
#include "djui_panel_host_settings.h"
|
||||
#include "djui_panel_host_mods.h"
|
||||
#include "djui_panel_host_save.h"
|
||||
#include "djui_panel_host_message.h"
|
||||
#include "djui_panel_join.h"
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#endif
|
||||
|
||||
struct DjuiInputbox* sInputboxPort = NULL;
|
||||
static unsigned int sKnockbackIndex = 0;
|
||||
|
||||
static void djui_panel_host_network_system_change(UNUSED struct DjuiBase* base) {
|
||||
djui_base_set_enabled(&sInputboxPort->base, DJUI_HOST_NS_IS_SOCKET);
|
||||
|
@ -40,14 +39,6 @@ static void djui_panel_host_port_text_change(struct DjuiBase* caller) {
|
|||
}
|
||||
}
|
||||
|
||||
static void djui_panel_host_knockback_change(UNUSED struct DjuiBase* caller) {
|
||||
switch (sKnockbackIndex) {
|
||||
case 0: configPlayerKnockbackStrength = 10; break;
|
||||
case 1: configPlayerKnockbackStrength = 25; break;
|
||||
default: configPlayerKnockbackStrength = 75; break;
|
||||
}
|
||||
}
|
||||
|
||||
static void djui_panel_host_do_host(struct DjuiBase* caller) {
|
||||
if (!djui_panel_host_port_valid()) {
|
||||
djui_interactable_set_input_focus(&sInputboxPort->base);
|
||||
|
@ -59,11 +50,7 @@ static void djui_panel_host_do_host(struct DjuiBase* caller) {
|
|||
}
|
||||
|
||||
void djui_panel_host_create(struct DjuiBase* caller) {
|
||||
#ifdef DISCORD_SDK
|
||||
f32 bodyHeight = 32 * 8 + 64 * 2 + 16 * 10;
|
||||
#else
|
||||
f32 bodyHeight = 32 * 7 + 64 * 2 + 16 * 9;
|
||||
#endif
|
||||
f32 bodyHeight = 32 * 3 + 64 * 3 + 16 * 5;
|
||||
|
||||
struct DjuiBase* defaultBase = NULL;
|
||||
struct DjuiThreePanel* panel = djui_panel_menu_create(bodyHeight, "\\#ff0800\\H\\#1be700\\O\\#00b3ff\\S\\#ffef00\\T");
|
||||
|
@ -122,39 +109,17 @@ void djui_panel_host_create(struct DjuiBase* caller) {
|
|||
djui_interactable_hook_click(&button1->base, djui_panel_host_save_create);
|
||||
}
|
||||
|
||||
char* iChoices[3] = { "Non-solid", "Solid", "Friendly Fire" };
|
||||
struct DjuiSelectionbox* selectionbox2 = djui_selectionbox_create(&body->base, "Player interaction", iChoices, 3, &configPlayerInteraction);
|
||||
djui_base_set_size_type(&selectionbox2->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&selectionbox2->base, 1.0f, 32);
|
||||
struct DjuiButton* button1 = djui_button_create(&body->base, "Settings");
|
||||
djui_base_set_size_type(&button1->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&button1->base, 1.0f, 64);
|
||||
djui_base_set_alignment(&button1->base, DJUI_HALIGN_CENTER, DJUI_VALIGN_TOP);
|
||||
djui_interactable_hook_click(&button1->base, djui_panel_host_settings_create);
|
||||
|
||||
char* kChoices[3] = { "Weak", "Normal", "Too much" };
|
||||
sKnockbackIndex = (configPlayerKnockbackStrength <= 20)
|
||||
? 0
|
||||
: ((configPlayerKnockbackStrength <= 40) ? 1 : 2);
|
||||
struct DjuiSelectionbox* selectionbox3 = djui_selectionbox_create(&body->base, "Knockback strength", kChoices, 3, &sKnockbackIndex);
|
||||
djui_base_set_size_type(&selectionbox3->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&selectionbox3->base, 1.0f, 32);
|
||||
djui_interactable_hook_value_change(&selectionbox3->base, djui_panel_host_knockback_change);
|
||||
|
||||
struct DjuiCheckbox* checkbox1 = djui_checkbox_create(&body->base, "Stay in level after star", &configStayInLevelAfterStar);
|
||||
djui_base_set_size_type(&checkbox1->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&checkbox1->base, 1.0f, 32);
|
||||
|
||||
struct DjuiCheckbox* checkbox2 = djui_checkbox_create(&body->base, "Skip intro cutscene", &configSkipIntro);
|
||||
djui_base_set_size_type(&checkbox2->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&checkbox2->base, 1.0f, 32);
|
||||
|
||||
struct DjuiCheckbox* checkbox3 = djui_checkbox_create(&body->base, "Share lives", &configShareLives);
|
||||
djui_base_set_size_type(&checkbox3->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&checkbox3->base, 1.0f, 32);
|
||||
|
||||
struct DjuiCheckbox* checkbox4 = djui_checkbox_create(&body->base, "Enable cheats", &configEnableCheats);
|
||||
djui_base_set_size_type(&checkbox4->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&checkbox4->base, 1.0f, 32);
|
||||
|
||||
struct DjuiCheckbox* checkbox5 = djui_checkbox_create(&body->base, "Bubble on death", &configBubbleDeath);
|
||||
djui_base_set_size_type(&checkbox5->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&checkbox5->base, 1.0f, 32);
|
||||
struct DjuiButton* button2 = djui_button_create(&body->base, "Mods");
|
||||
djui_base_set_size_type(&button2->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&button2->base, 1.0f, 64);
|
||||
djui_base_set_alignment(&button2->base, DJUI_HALIGN_CENTER, DJUI_VALIGN_TOP);
|
||||
djui_interactable_hook_click(&button2->base, djui_panel_host_mods_create);
|
||||
|
||||
struct DjuiRect* rect3 = djui_rect_create(&body->base);
|
||||
djui_base_set_size_type(&rect3->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
|
|
33
src/pc/djui/djui_panel_host_mods.c
Normal file
33
src/pc/djui/djui_panel_host_mods.c
Normal file
|
@ -0,0 +1,33 @@
|
|||
#include <stdio.h>
|
||||
#include "djui.h"
|
||||
#include "game/save_file.h"
|
||||
#include "pc/network/network.h"
|
||||
#include "pc/utils/misc.h"
|
||||
#include "pc/configfile.h"
|
||||
#include "pc/cheats.h"
|
||||
#include "pc/mod_list.h"
|
||||
|
||||
void djui_panel_host_mods_create(struct DjuiBase* caller) {
|
||||
f32 bodyHeight = 32 * gModTableLocal.entryCount + 64 * 1 + 16 * (gModTableLocal.entryCount + 1);
|
||||
|
||||
struct DjuiBase* defaultBase = NULL;
|
||||
struct DjuiThreePanel* panel = djui_panel_menu_create(bodyHeight, "\\#ff0800\\M\\#1be700\\O\\#00b3ff\\D\\#ffef00\\S");
|
||||
struct DjuiFlowLayout* body = (struct DjuiFlowLayout*)djui_three_panel_get_body(panel);
|
||||
{
|
||||
for (int i = 0; i < gModTableLocal.entryCount; i++) {
|
||||
struct ModListEntry* entry = &gModTableLocal.entries[i];
|
||||
struct DjuiCheckbox* checkbox = djui_checkbox_create(&body->base, entry->name, &entry->enabled);
|
||||
djui_base_set_size_type(&checkbox->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&checkbox->base, 1.0f, 32);
|
||||
}
|
||||
|
||||
struct DjuiButton* button1 = djui_button_create(&body->base, "Back");
|
||||
djui_base_set_size_type(&button1->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&button1->base, 1.0f, 64);
|
||||
djui_button_set_style(button1, 1);
|
||||
djui_interactable_hook_click(&button1->base, djui_panel_menu_back);
|
||||
defaultBase = &button1->base;
|
||||
}
|
||||
|
||||
djui_panel_add(caller, &panel->base, defaultBase);
|
||||
}
|
4
src/pc/djui/djui_panel_host_mods.h
Normal file
4
src/pc/djui/djui_panel_host_mods.h
Normal file
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
#include "djui.h"
|
||||
|
||||
void djui_panel_host_mods_create(struct DjuiBase* caller);
|
69
src/pc/djui/djui_panel_host_settings.c
Normal file
69
src/pc/djui/djui_panel_host_settings.c
Normal file
|
@ -0,0 +1,69 @@
|
|||
#include <stdio.h>
|
||||
#include "djui.h"
|
||||
#include "game/save_file.h"
|
||||
#include "pc/network/network.h"
|
||||
#include "pc/utils/misc.h"
|
||||
#include "pc/configfile.h"
|
||||
#include "pc/cheats.h"
|
||||
|
||||
static unsigned int sKnockbackIndex = 0;
|
||||
|
||||
static void djui_panel_host_settings_knockback_change(UNUSED struct DjuiBase* caller) {
|
||||
switch (sKnockbackIndex) {
|
||||
case 0: configPlayerKnockbackStrength = 10; break;
|
||||
case 1: configPlayerKnockbackStrength = 25; break;
|
||||
default: configPlayerKnockbackStrength = 75; break;
|
||||
}
|
||||
}
|
||||
|
||||
void djui_panel_host_settings_create(struct DjuiBase* caller) {
|
||||
f32 bodyHeight = 32 * 7 + 64 * 1 + 16 * 7;
|
||||
|
||||
struct DjuiBase* defaultBase = NULL;
|
||||
struct DjuiThreePanel* panel = djui_panel_menu_create(bodyHeight, "\\#ff0800\\S\\#1be700\\E\\#00b3ff\\T\\#ffef00\\T\\#ff0800\\I\\#1be700\\N\\#00b3ff\\G\\#ffef00\\S");
|
||||
struct DjuiFlowLayout* body = (struct DjuiFlowLayout*)djui_three_panel_get_body(panel);
|
||||
{
|
||||
char* iChoices[3] = { "Non-solid", "Solid", "Friendly Fire" };
|
||||
struct DjuiSelectionbox* selectionbox1 = djui_selectionbox_create(&body->base, "Player interaction", iChoices, 3, &configPlayerInteraction);
|
||||
djui_base_set_size_type(&selectionbox1->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&selectionbox1->base, 1.0f, 32);
|
||||
|
||||
char* kChoices[3] = { "Weak", "Normal", "Too much" };
|
||||
sKnockbackIndex = (configPlayerKnockbackStrength <= 20)
|
||||
? 0
|
||||
: ((configPlayerKnockbackStrength <= 40) ? 1 : 2);
|
||||
struct DjuiSelectionbox* selectionbox2 = djui_selectionbox_create(&body->base, "Knockback strength", kChoices, 3, &sKnockbackIndex);
|
||||
djui_base_set_size_type(&selectionbox2->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&selectionbox2->base, 1.0f, 32);
|
||||
djui_interactable_hook_value_change(&selectionbox2->base, djui_panel_host_settings_knockback_change);
|
||||
|
||||
struct DjuiCheckbox* checkbox1 = djui_checkbox_create(&body->base, "Stay in level after star", &configStayInLevelAfterStar);
|
||||
djui_base_set_size_type(&checkbox1->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&checkbox1->base, 1.0f, 32);
|
||||
|
||||
struct DjuiCheckbox* checkbox2 = djui_checkbox_create(&body->base, "Skip intro cutscene", &configSkipIntro);
|
||||
djui_base_set_size_type(&checkbox2->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&checkbox2->base, 1.0f, 32);
|
||||
|
||||
struct DjuiCheckbox* checkbox3 = djui_checkbox_create(&body->base, "Share lives", &configShareLives);
|
||||
djui_base_set_size_type(&checkbox3->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&checkbox3->base, 1.0f, 32);
|
||||
|
||||
struct DjuiCheckbox* checkbox4 = djui_checkbox_create(&body->base, "Enable cheats", &configEnableCheats);
|
||||
djui_base_set_size_type(&checkbox4->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&checkbox4->base, 1.0f, 32);
|
||||
|
||||
struct DjuiCheckbox* checkbox5 = djui_checkbox_create(&body->base, "Bubble on death", &configBubbleDeath);
|
||||
djui_base_set_size_type(&checkbox5->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&checkbox5->base, 1.0f, 32);
|
||||
|
||||
struct DjuiButton* button1 = djui_button_create(&body->base, "Back");
|
||||
djui_base_set_size_type(&button1->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&button1->base, 1.0f, 64);
|
||||
djui_button_set_style(button1, 1);
|
||||
djui_interactable_hook_click(&button1->base, djui_panel_menu_back);
|
||||
defaultBase = &button1->base;
|
||||
}
|
||||
|
||||
djui_panel_add(caller, &panel->base, defaultBase);
|
||||
}
|
4
src/pc/djui/djui_panel_host_settings.h
Normal file
4
src/pc/djui/djui_panel_host_settings.h
Normal file
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
#include "djui.h"
|
||||
|
||||
void djui_panel_host_settings_create(struct DjuiBase* caller);
|
|
@ -92,8 +92,10 @@ void smlua_init(void) {
|
|||
|
||||
// load scripts
|
||||
LOG_INFO("Loading scripts:");
|
||||
for (int i = 0; i < gModEntryCount; i++) {
|
||||
struct ModListEntry* entry = &gModEntries[i];
|
||||
struct ModTable* table = (gNetworkType == NT_SERVER) ? &gModTableLocal : &gModTableRemote;
|
||||
for (int i = 0; i < table->entryCount; i++) {
|
||||
struct ModListEntry* entry = &table->entries[i];
|
||||
if (!entry->enabled) { continue; }
|
||||
LOG_INFO(" %s", entry->path);
|
||||
smlua_load_script(entry->path);
|
||||
}
|
||||
|
|
|
@ -6,9 +6,8 @@
|
|||
|
||||
#define MAX_SESSION_CHARS 7
|
||||
|
||||
struct ModListEntry* gModEntries = NULL;
|
||||
u16 gModEntryCount = 0;
|
||||
u64 gModTotalSize = 0;
|
||||
struct ModTable gModTableLocal = { .entries = NULL, .entryCount = 0, .totalSize = 0, .isRemote = false };
|
||||
struct ModTable gModTableRemote = { .entries = NULL, .entryCount = 0, .totalSize = 0, .isRemote = true };
|
||||
|
||||
static char sTmpSession[MAX_SESSION_CHARS] = { 0 };
|
||||
static char sTmpPath[PATH_MAX] = { 0 };
|
||||
|
@ -18,82 +17,6 @@ static bool acceptable_file(char* string) {
|
|||
return (string != NULL && !strcmp(string, ".lua"));
|
||||
}
|
||||
|
||||
void mod_list_alloc(u16 count) {
|
||||
mod_list_clear();
|
||||
gModEntryCount = count;
|
||||
gModEntries = (struct ModListEntry*)calloc(gModEntryCount, sizeof(struct ModListEntry));
|
||||
}
|
||||
|
||||
void mod_list_add(u16 index, char* name, size_t size, bool tmpFile) {
|
||||
if (!acceptable_file(name)) { return; }
|
||||
struct ModListEntry* entry = &gModEntries[index];
|
||||
entry->name = name;
|
||||
entry->size = size;
|
||||
gModTotalSize += size;
|
||||
|
||||
if (tmpFile) {
|
||||
snprintf(entry->path, PATH_MAX - 1, "%s/%s-%s", sTmpPath, sTmpSession, name);
|
||||
} else {
|
||||
snprintf(entry->path, PATH_MAX - 1, "%s/%s", MOD_PATH, name);
|
||||
}
|
||||
|
||||
entry->fp = fopen(entry->path, tmpFile ? "wb" : "rb");
|
||||
|
||||
if (!tmpFile) {
|
||||
fseek(entry->fp, 0, SEEK_END);
|
||||
entry->size = ftell(entry->fp);
|
||||
fseek(entry->fp, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
entry->complete = !tmpFile;
|
||||
}
|
||||
|
||||
void mod_list_load(void) {
|
||||
struct dirent* dir;
|
||||
DIR* d = opendir(MOD_PATH);
|
||||
if (!d) { closedir(d); return; }
|
||||
|
||||
u16 count = 0;
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
if (!acceptable_file(dir->d_name)) { continue; }
|
||||
count++;
|
||||
}
|
||||
|
||||
mod_list_alloc(count);
|
||||
|
||||
rewinddir(d);
|
||||
u16 index = 0;
|
||||
|
||||
LOG_INFO("Loading mods:");
|
||||
gModTotalSize = 0;
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
if (!acceptable_file(dir->d_name)) { continue; }
|
||||
LOG_INFO(" %s", dir->d_name);
|
||||
mod_list_add(index++, strdup(dir->d_name), 0, false);
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
}
|
||||
|
||||
void mod_list_clear(void) {
|
||||
for (int i = 0; i < gModEntryCount; i++) {
|
||||
struct ModListEntry* entry = &gModEntries[i];
|
||||
if (entry->name != NULL) {
|
||||
free(entry->name);
|
||||
entry->name = NULL;
|
||||
}
|
||||
if (entry->fp != NULL) {
|
||||
fclose(entry->fp);
|
||||
entry->fp = NULL;
|
||||
}
|
||||
entry->size = 0;
|
||||
}
|
||||
if (gModEntries != NULL) {
|
||||
free(gModEntries);
|
||||
gModEntries = NULL;
|
||||
}
|
||||
gModEntryCount = 0;
|
||||
}
|
||||
|
||||
static void mod_list_delete_tmp(void) {
|
||||
struct dirent* dir;
|
||||
|
@ -122,13 +45,98 @@ static void mod_list_delete_tmp(void) {
|
|||
closedir(d);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
void mod_list_add(u16 index, char* name, size_t size, bool tmpFile) {
|
||||
if (!acceptable_file(name)) { return; }
|
||||
struct ModTable* table = tmpFile ? &gModTableRemote : &gModTableLocal;
|
||||
|
||||
struct ModListEntry* entry = &table->entries[index];
|
||||
entry->name = name;
|
||||
entry->size = size;
|
||||
table->totalSize += size;
|
||||
|
||||
if (tmpFile) {
|
||||
snprintf(entry->path, PATH_MAX - 1, "%s/%s-%s", sTmpPath, sTmpSession, name);
|
||||
}
|
||||
else {
|
||||
snprintf(entry->path, PATH_MAX - 1, "%s/%s", MOD_PATH, name);
|
||||
}
|
||||
|
||||
entry->fp = fopen(entry->path, tmpFile ? "wb" : "rb");
|
||||
|
||||
if (!tmpFile) {
|
||||
fseek(entry->fp, 0, SEEK_END);
|
||||
entry->size = ftell(entry->fp);
|
||||
fseek(entry->fp, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
entry->complete = !tmpFile;
|
||||
entry->enabled = false;
|
||||
}
|
||||
|
||||
void mod_table_clear(struct ModTable* table) {
|
||||
for (int i = 0; i < table->entryCount; i++) {
|
||||
struct ModListEntry* entry = &table->entries[i];
|
||||
if (entry->name != NULL) {
|
||||
free(entry->name);
|
||||
entry->name = NULL;
|
||||
}
|
||||
if (entry->fp != NULL) {
|
||||
fclose(entry->fp);
|
||||
entry->fp = NULL;
|
||||
}
|
||||
entry->size = 0;
|
||||
}
|
||||
if (table->entries != NULL) {
|
||||
free(table->entries);
|
||||
table->entries = NULL;
|
||||
}
|
||||
table->entryCount = 0;
|
||||
table->totalSize = 0;
|
||||
}
|
||||
|
||||
void mod_list_alloc(struct ModTable* table, u16 count) {
|
||||
mod_table_clear(table);
|
||||
table->entryCount = count;
|
||||
table->entries = (struct ModListEntry*)calloc(count, sizeof(struct ModListEntry));
|
||||
}
|
||||
|
||||
static void mod_list_load_local(void) {
|
||||
struct dirent* dir;
|
||||
DIR* d = opendir(MOD_PATH);
|
||||
if (!d) { closedir(d); return; }
|
||||
|
||||
u16 count = 0;
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
if (!acceptable_file(dir->d_name)) { continue; }
|
||||
count++;
|
||||
}
|
||||
|
||||
mod_list_alloc(&gModTableLocal, count);
|
||||
|
||||
rewinddir(d);
|
||||
u16 index = 0;
|
||||
|
||||
LOG_INFO("Loading mods:");
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
if (!acceptable_file(dir->d_name)) { continue; }
|
||||
LOG_INFO(" %s", dir->d_name);
|
||||
mod_list_add(index++, strdup(dir->d_name), 0, false);
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
}
|
||||
|
||||
void mod_list_init(void) {
|
||||
snprintf(sTmpSession, MAX_SESSION_CHARS, "%06X", (u32)(rand() % 0xFFFFFF));
|
||||
snprintf(sTmpPath, PATH_MAX - 1, "%s", fs_get_write_path("tmp"));
|
||||
if (!fs_sys_dir_exists(sTmpPath)) { fs_sys_mkdir(sTmpPath); }
|
||||
mod_list_load_local();
|
||||
}
|
||||
|
||||
void mod_list_shutdown(void) {
|
||||
mod_list_clear();
|
||||
mod_table_clear(&gModTableLocal);
|
||||
mod_table_clear(&gModTableRemote);
|
||||
mod_list_delete_tmp();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,24 +8,33 @@
|
|||
|
||||
#define MOD_PATH "./mods"
|
||||
|
||||
#pragma pack(1)
|
||||
struct ModListEntry {
|
||||
char* name;
|
||||
FILE* fp;
|
||||
char path[PATH_MAX];
|
||||
size_t size;
|
||||
u64 curOffset;
|
||||
u16 remoteIndex;
|
||||
bool tmp;
|
||||
bool complete;
|
||||
bool enabled;
|
||||
};
|
||||
extern struct ModListEntry* gModEntries;
|
||||
extern u16 gModEntryCount;
|
||||
extern u64 gModTotalSize;
|
||||
|
||||
void mod_list_alloc(u16 count);
|
||||
#pragma pack(1)
|
||||
struct ModTable {
|
||||
struct ModListEntry* entries;
|
||||
u16 entryCount;
|
||||
u8 isRemote;
|
||||
u64 totalSize;
|
||||
};
|
||||
|
||||
extern struct ModTable gModTableLocal;
|
||||
extern struct ModTable gModTableRemote;
|
||||
|
||||
void mod_list_add(u16 index, char* name, size_t size, bool tmpFile);
|
||||
|
||||
void mod_list_load(void);
|
||||
void mod_list_clear(void);
|
||||
void mod_table_clear(struct ModTable* table);
|
||||
void mod_list_alloc(struct ModTable* table, u16 count);
|
||||
|
||||
void mod_list_init(void);
|
||||
void mod_list_shutdown(void);
|
||||
|
|
|
@ -97,7 +97,6 @@ bool network_init(enum NetworkType inNetworkType) {
|
|||
gNetworkType = inNetworkType;
|
||||
|
||||
if (gNetworkType == NT_SERVER) {
|
||||
mod_list_load();
|
||||
smlua_init();
|
||||
|
||||
network_player_connected(NPT_LOCAL, 0, configPlayerModel, configPlayerPalette, configPlayerName);
|
||||
|
|
|
@ -289,14 +289,15 @@ void network_receive_player_settings(struct Packet* p);
|
|||
|
||||
// packet_mod_list.c
|
||||
void network_send_mod_list_request(void);
|
||||
void network_receive_mod_list_request(struct Packet* p);
|
||||
void network_receive_mod_list_request(UNUSED struct Packet* p);
|
||||
void network_send_mod_list(void);
|
||||
void network_receive_mod_list(struct Packet* p);
|
||||
|
||||
// packet_download.c
|
||||
void network_send_download_request(u16 index, u64 offset);
|
||||
void network_send_next_download_request(void);
|
||||
void network_send_download_request(u16 clientIndex, u16 serverIndex, u64 offset);
|
||||
void network_receive_download_request(struct Packet* p);
|
||||
void network_send_download(u16 index, u64 offset);
|
||||
void network_send_download(u16 clientIndex, u16 serverIndex, u64 offset);
|
||||
void network_receive_download(struct Packet* p);
|
||||
|
||||
|
||||
|
|
|
@ -11,32 +11,33 @@ static bool sWaitingForOffset[OFFSET_COUNT] = { 0 };
|
|||
u64 sTotalDownloadBytes = 0;
|
||||
extern float gDownloadProgress;
|
||||
|
||||
static void network_send_next_download_request(void) {
|
||||
void network_send_next_download_request(void) {
|
||||
SOFT_ASSERT(gNetworkType == NT_CLIENT);
|
||||
for (int i = 0; i < gModEntryCount; i++) {
|
||||
struct ModListEntry* entry = &gModEntries[i];
|
||||
for (int i = 0; i < gModTableRemote.entryCount; i++) {
|
||||
struct ModListEntry* entry = &gModTableRemote.entries[i];
|
||||
if (entry->complete) { continue; }
|
||||
network_send_download_request(i, entry->curOffset);
|
||||
network_send_download_request(i, entry->remoteIndex, entry->curOffset);
|
||||
return;
|
||||
}
|
||||
network_send_join_request();
|
||||
}
|
||||
|
||||
void network_send_download_request(u16 index, u64 offset) {
|
||||
void network_send_download_request(u16 clientIndex, u16 serverIndex, u64 offset) {
|
||||
SOFT_ASSERT(gNetworkType == NT_CLIENT);
|
||||
|
||||
struct Packet p = { 0 };
|
||||
packet_init(&p, PACKET_DOWNLOAD_REQUEST, true, PLMT_NONE);
|
||||
|
||||
packet_write(&p, &index, sizeof(u16));
|
||||
packet_write(&p, &clientIndex, sizeof(u16));
|
||||
packet_write(&p, &serverIndex, sizeof(u16));
|
||||
packet_write(&p, &offset, sizeof(u64));
|
||||
|
||||
if (index == 0 && offset == 0) {
|
||||
if (clientIndex == 0 && offset == 0) {
|
||||
sTotalDownloadBytes = 0;
|
||||
gDownloadProgress = 0;
|
||||
}
|
||||
|
||||
struct ModListEntry* entry = &gModEntries[index];
|
||||
struct ModListEntry* entry = &gModTableRemote.entries[clientIndex];
|
||||
for (int i = 0; i < OFFSET_COUNT; i++) {
|
||||
sOffset[i] = offset + CHUNK_SIZE * i;
|
||||
sWaitingForOffset[i] = (sOffset[i] < entry->size);
|
||||
|
@ -48,40 +49,42 @@ void network_send_download_request(u16 index, u64 offset) {
|
|||
void network_receive_download_request(struct Packet* p) {
|
||||
SOFT_ASSERT(gNetworkType == NT_SERVER);
|
||||
|
||||
u16 index;
|
||||
u16 clientIndex;
|
||||
u16 serverIndex;
|
||||
u64 offset;
|
||||
packet_read(p, &index, sizeof(u16));
|
||||
packet_read(p, &clientIndex, sizeof(u16));
|
||||
packet_read(p, &serverIndex, sizeof(u16));
|
||||
packet_read(p, &offset, sizeof(u64));
|
||||
|
||||
struct ModListEntry* entry = &gModEntries[index];
|
||||
if (index >= gModEntryCount) {
|
||||
LOG_ERROR("Requested download of invalid index %u:%llu", index, offset);
|
||||
struct ModListEntry* entry = &gModTableLocal.entries[serverIndex];
|
||||
if (serverIndex >= gModTableLocal.entryCount) {
|
||||
LOG_ERROR("Requested download of invalid index %u:%llu", serverIndex, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < OFFSET_COUNT; i++) {
|
||||
u64 o = offset + CHUNK_SIZE * i;
|
||||
if (o >= entry->size) { break; }
|
||||
network_send_download(index, o);
|
||||
network_send_download(clientIndex, serverIndex, o);
|
||||
}
|
||||
}
|
||||
|
||||
void network_send_download(u16 index, u64 offset) {
|
||||
void network_send_download(u16 clientIndex, u16 serverIndex, u64 offset) {
|
||||
SOFT_ASSERT(gNetworkType == NT_SERVER);
|
||||
|
||||
if (index >= gModEntryCount) {
|
||||
LOG_ERROR("Requested download of invalid index %u:%llu", index, offset);
|
||||
if (serverIndex >= gModTableLocal.entryCount) {
|
||||
LOG_ERROR("Requested download of invalid index %u:%llu", serverIndex, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
struct ModListEntry* entry = &gModEntries[index];
|
||||
struct ModListEntry* entry = &gModTableLocal.entries[serverIndex];
|
||||
if (offset >= entry->size) {
|
||||
LOG_ERROR("Requested download of invalid offset %u:%llu", index, offset);
|
||||
LOG_ERROR("Requested download of invalid offset %u:%llu", serverIndex, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry->fp == NULL) {
|
||||
LOG_ERROR("Requested download of invalid file pointer %u:%llu", index, offset);
|
||||
LOG_ERROR("Requested download of invalid file pointer %u:%llu", serverIndex, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -97,7 +100,8 @@ void network_send_download(u16 index, u64 offset) {
|
|||
struct Packet p = { 0 };
|
||||
packet_init(&p, PACKET_DOWNLOAD, true, PLMT_NONE);
|
||||
|
||||
packet_write(&p, &index, sizeof(u16));
|
||||
packet_write(&p, &clientIndex, sizeof(u16));
|
||||
packet_write(&p, &serverIndex, sizeof(u16));
|
||||
packet_write(&p, &offset, sizeof(u64));
|
||||
packet_write(&p, &chunkSize, sizeof(u16));
|
||||
packet_write(&p, chunk, chunkSize * sizeof(u8));
|
||||
|
@ -112,34 +116,36 @@ void network_receive_download(struct Packet* p) {
|
|||
return;
|
||||
}
|
||||
|
||||
u16 index;
|
||||
u16 clientIndex;
|
||||
u16 serverIndex;
|
||||
u64 offset;
|
||||
u16 chunkSize;
|
||||
u8 chunk[400] = { 0 };
|
||||
|
||||
packet_read(p, &index, sizeof(u16));
|
||||
packet_read(p, &clientIndex, sizeof(u16));
|
||||
packet_read(p, &serverIndex, sizeof(u16));
|
||||
packet_read(p, &offset, sizeof(u64));
|
||||
packet_read(p, &chunkSize, sizeof(u16));
|
||||
packet_read(p, chunk, chunkSize * sizeof(u8));
|
||||
|
||||
if (index >= gModEntryCount) {
|
||||
LOG_ERROR("Received download of invalid index %u:%llu", index, offset);
|
||||
if (clientIndex >= gModTableRemote.entryCount) {
|
||||
LOG_ERROR("Received download of invalid index %u:%llu", clientIndex, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
struct ModListEntry* entry = &gModEntries[index];
|
||||
struct ModListEntry* entry = &gModTableRemote.entries[clientIndex];
|
||||
if (offset >= entry->size) {
|
||||
LOG_ERROR("Received download of invalid offset %u:%llu", index, offset);
|
||||
LOG_ERROR("Received download of invalid offset %u:%llu", clientIndex, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry->fp == NULL) {
|
||||
LOG_ERROR("Received download of invalid file pointer %u:%llu", index, offset);
|
||||
LOG_ERROR("Received download of invalid file pointer %u:%llu", clientIndex, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((offset + chunkSize) > entry->size) {
|
||||
LOG_ERROR("Received download of invalid chunk size %u:%llu:%u", index, offset, chunkSize);
|
||||
LOG_ERROR("Received download of invalid chunk size %u:%llu:%u", clientIndex, offset, chunkSize);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -165,7 +171,7 @@ void network_receive_download(struct Packet* p) {
|
|||
|
||||
// update progress
|
||||
sTotalDownloadBytes += chunkSize;
|
||||
gDownloadProgress = (float)sTotalDownloadBytes / (float)gModTotalSize;
|
||||
gDownloadProgress = (float)sTotalDownloadBytes / (float)gModTableRemote.totalSize;
|
||||
|
||||
if (!waiting) {
|
||||
// check if we're finished with this file
|
||||
|
|
|
@ -1,40 +1,24 @@
|
|||
#include <stdio.h>
|
||||
#include "../network.h"
|
||||
#include "pc/mod_list.h"
|
||||
#include "pc/djui/djui.h"
|
||||
#include "pc/debuglog.h"
|
||||
|
||||
void network_send_mod_list_request(void) {
|
||||
SOFT_ASSERT(gNetworkType == NT_CLIENT);
|
||||
mod_list_shutdown();
|
||||
mod_table_clear(&gModTableRemote);
|
||||
|
||||
struct Packet p = { 0 };
|
||||
packet_init(&p, PACKET_MOD_LIST_REQUEST, true, PLMT_NONE);
|
||||
|
||||
char version[MAX_VERSION_LENGTH] = { 0 };
|
||||
snprintf(version, MAX_VERSION_LENGTH, "%s", get_version());
|
||||
packet_write(&p, &version, sizeof(u8) * MAX_VERSION_LENGTH);
|
||||
|
||||
network_send_to((gNetworkPlayerServer != NULL) ? gNetworkPlayerServer->localIndex : 0, &p);
|
||||
LOG_INFO("sending mod list request");
|
||||
}
|
||||
|
||||
void network_receive_mod_list_request(struct Packet* p) {
|
||||
void network_receive_mod_list_request(UNUSED struct Packet* p) {
|
||||
SOFT_ASSERT(gNetworkType == NT_SERVER);
|
||||
LOG_INFO("received mod list request");
|
||||
|
||||
char version[MAX_VERSION_LENGTH] = { 0 };
|
||||
snprintf(version, MAX_VERSION_LENGTH, "%s", get_version());
|
||||
|
||||
char remoteVersion[MAX_VERSION_LENGTH] = { 0 };
|
||||
packet_read(p, &remoteVersion, sizeof(u8) * MAX_VERSION_LENGTH);
|
||||
LOG_INFO("client has version: %s", remoteVersion);
|
||||
|
||||
if (memcmp(version, remoteVersion, MAX_VERSION_LENGTH) != 0) {
|
||||
LOG_INFO("client version mismatch: %s != %s", remoteVersion, version);
|
||||
// TODO: send version mismatch packet
|
||||
return;
|
||||
}
|
||||
|
||||
network_send_mod_list();
|
||||
}
|
||||
|
||||
|
@ -44,11 +28,24 @@ void network_send_mod_list(void) {
|
|||
struct Packet p = { 0 };
|
||||
packet_init(&p, PACKET_MOD_LIST, true, PLMT_NONE);
|
||||
|
||||
packet_write(&p, &gModEntryCount, sizeof(u16));
|
||||
LOG_INFO("sent mod list (%u):", gModEntryCount);
|
||||
for (int i = 0; i < gModEntryCount; i++) {
|
||||
struct ModListEntry* entry = &gModEntries[i];
|
||||
char version[MAX_VERSION_LENGTH] = { 0 };
|
||||
snprintf(version, MAX_VERSION_LENGTH, "%s", get_version());
|
||||
LOG_INFO("sending version: %s", version);
|
||||
packet_write(&p, &version, sizeof(u8) * MAX_VERSION_LENGTH);
|
||||
|
||||
u16 activeCount = 0;
|
||||
for (u16 i = 0; i < gModTableLocal.entryCount; i++) {
|
||||
struct ModListEntry* entry = &gModTableLocal.entries[i];
|
||||
if (entry->enabled) { activeCount++; }
|
||||
}
|
||||
|
||||
packet_write(&p, &activeCount, sizeof(u16));
|
||||
LOG_INFO("sent mod list (%u):", gModTableLocal.entryCount);
|
||||
for (u16 i = 0; i < gModTableLocal.entryCount; i++) {
|
||||
struct ModListEntry* entry = &gModTableLocal.entries[i];
|
||||
if (!entry->enabled) { continue; }
|
||||
u16 nameLength = strlen(entry->name);
|
||||
packet_write(&p, &i, sizeof(u16));
|
||||
packet_write(&p, &nameLength, sizeof(u16));
|
||||
packet_write(&p, entry->name, sizeof(u8) * nameLength);
|
||||
packet_write(&p, &entry->size, sizeof(u16));
|
||||
|
@ -60,7 +57,7 @@ void network_send_mod_list(void) {
|
|||
void network_receive_mod_list(struct Packet* p) {
|
||||
SOFT_ASSERT(gNetworkType == NT_CLIENT);
|
||||
SOFT_ASSERT(p->localIndex == UNKNOWN_LOCAL_INDEX);
|
||||
if (gModEntries != NULL) {
|
||||
if (gModTableRemote.entries != NULL) {
|
||||
LOG_INFO("received mod list after allocating");
|
||||
return;
|
||||
}
|
||||
|
@ -69,13 +66,32 @@ void network_receive_mod_list(struct Packet* p) {
|
|||
gNetworkServerAddr = network_duplicate_address(0);
|
||||
}
|
||||
|
||||
char version[MAX_VERSION_LENGTH] = { 0 };
|
||||
snprintf(version, MAX_VERSION_LENGTH, "%s", get_version());
|
||||
LOG_INFO("client has version: %s", version);
|
||||
|
||||
// verify version
|
||||
char remoteVersion[MAX_VERSION_LENGTH] = { 0 };
|
||||
packet_read(p, &remoteVersion, sizeof(u8) * MAX_VERSION_LENGTH);
|
||||
LOG_INFO("server has version: %s", version);
|
||||
if (memcmp(version, remoteVersion, MAX_VERSION_LENGTH) != 0) {
|
||||
network_shutdown(true);
|
||||
LOG_ERROR("version mismatch");
|
||||
char mismatchMessage[256] = { 0 };
|
||||
snprintf(mismatchMessage, 256, "\\#ffa0a0\\Error:\\#c8c8c8\\ Version mismatch.\n\nYour version: \\#a0a0ff\\%s\\#c8c8c8\\\nTheir version: \\#a0a0ff\\%s\\#c8c8c8\\\n\nSomeone is out of date!\n", version, remoteVersion);
|
||||
djui_panel_join_message_error(mismatchMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
u16 modEntryCount = 0;
|
||||
packet_read(p, &modEntryCount, sizeof(u16));
|
||||
mod_list_alloc(modEntryCount);
|
||||
mod_list_alloc(&gModTableRemote, modEntryCount);
|
||||
|
||||
LOG_INFO("received mod list (%u):", modEntryCount);
|
||||
gModTotalSize = 0;
|
||||
for (int i = 0; i < modEntryCount; i++) {
|
||||
u16 remoteIndex = 0;
|
||||
packet_read(p, &remoteIndex, sizeof(u16));
|
||||
|
||||
u16 nameLength = 0;
|
||||
packet_read(p, &nameLength, sizeof(u16));
|
||||
|
||||
|
@ -86,12 +102,11 @@ void network_receive_mod_list(struct Packet* p) {
|
|||
packet_read(p, &size, sizeof(u16));
|
||||
|
||||
mod_list_add(i, name, size, true);
|
||||
gModTableRemote.entries[i].enabled = true;
|
||||
gModTableRemote.entries[i].remoteIndex = remoteIndex;
|
||||
|
||||
LOG_INFO(" '%s': %u", name, size);
|
||||
}
|
||||
|
||||
if (modEntryCount <= 0) {
|
||||
network_send_join_request();
|
||||
} else {
|
||||
network_send_download_request(0, 0);
|
||||
}
|
||||
network_send_next_download_request();
|
||||
}
|
||||
|
|
|
@ -221,6 +221,7 @@ void main_func(void) {
|
|||
const char *userpath = gCLIOpts.SavePath[0] ? gCLIOpts.SavePath : sys_user_path();
|
||||
fs_init(sys_ropaths, gamedir, userpath);
|
||||
|
||||
mod_list_init();
|
||||
configfile_load(configfile_name());
|
||||
if (configPlayerModel >= CT_MAX) { configPlayerModel = 0; }
|
||||
if (configPlayerPalette >= 16) { configPlayerPalette = 0; }
|
||||
|
@ -307,7 +308,6 @@ void main_func(void) {
|
|||
|
||||
audio_init();
|
||||
sound_init();
|
||||
mod_list_init();
|
||||
network_player_init();
|
||||
|
||||
thread5_game_loop(NULL);
|
||||
|
|
Loading…
Reference in a new issue