diff --git a/src/pc/configfile.c b/src/pc/configfile.c index 0edce7cc..4866eea7 100644 --- a/src/pc/configfile.c +++ b/src/pc/configfile.c @@ -13,7 +13,7 @@ #include "gfx/gfx_window_manager_api.h" #include "controller/controller_api.h" #include "fs/fs.h" -#include "pc/mod_list.h" +#include "pc/mods/mods.h" #include "pc/network/ban_list.h" #include "pc/crash_handler.h" @@ -217,21 +217,27 @@ static const struct ConfigOption options[] = { // FunctionConfigOption functions static void enable_mod_read(char** tokens, UNUSED int numTokens) { - for (unsigned int i = 0; i < gModTableLocal.entryCount; i++) { - struct ModListEntry* entry = &gModTableLocal.entries[i]; - if (!strcmp(tokens[1], entry->name)) { - entry->enabled = true; + char combined[256] = { 0 }; + for (int i = 1; i < numTokens; i++) { + if (i != 1) { strncat(combined, " ", 255); } + strncat(combined, tokens[i], 255); + } + + for (unsigned int i = 0; i < gLocalMods.entryCount; i++) { + struct Mod* mod = gLocalMods.entries[i]; + if (!strcmp(combined, mod->relativePath)) { + mod->enabled = true; break; } } } static void enable_mod_write(FILE* file) { - for (unsigned int i = 0; i < gModTableLocal.entryCount; i++) { - struct ModListEntry* entry = &gModTableLocal.entries[i]; - if (entry == NULL) { continue; } - if (!entry->enabled) { continue; } - fprintf(file, "%s %s\n", "enable-mod:", entry->name); + for (unsigned int i = 0; i < gLocalMods.entryCount; i++) { + struct Mod* mod = gLocalMods.entries[i]; + if (mod == NULL) { continue; } + if (!mod->enabled) { continue; } + fprintf(file, "%s %s\n", "enable-mod:", mod->relativePath); } } diff --git a/src/pc/djui/djui_panel_host_mods.c b/src/pc/djui/djui_panel_host_mods.c index 0e7baace..09d49d08 100644 --- a/src/pc/djui/djui_panel_host_mods.c +++ b/src/pc/djui/djui_panel_host_mods.c @@ -5,7 +5,8 @@ #include "pc/utils/misc.h" #include "pc/configfile.h" #include "pc/cheats.h" -#include "pc/mod_list.h" +#include "pc/mods/mods.h" +#include "pc/mods/mods_utils.h" static struct DjuiFlowLayout* sModLayout = NULL; static struct DjuiThreePanel* sDescriptionPanel = NULL; @@ -43,10 +44,11 @@ static void djui_panel_host_mods_description_create() { static void djui_mod_checkbox_on_hover(struct DjuiBase* base) { char* description = ""; - if (base->tag >= 0 && base->tag < gModTableLocal.entryCount) { - char* d = gModTableLocal.entries[base->tag].description; + if (base->tag >= 0 && base->tag < gLocalMods.entryCount) { + struct Mod* mod = gLocalMods.entries[base->tag]; + char* d = mod->description; if (d != NULL) { - description = gModTableLocal.entries[base->tag].description; + description = mod->description; } } djui_text_set_text(sTooltip, description); @@ -57,15 +59,15 @@ static void djui_mod_checkbox_on_hover_end(UNUSED struct DjuiBase* base) { } static void djui_mod_checkbox_on_value_change(UNUSED struct DjuiBase* base) { - mod_list_update_selectable(); + mods_update_selectable(); u16 index = 0; struct DjuiBaseChild* node = sModLayout->base.child; while (node != NULL) { - if (index >= gModTableLocal.entryCount) { break; } - struct ModListEntry* entry = &gModTableLocal.entries[index]; + if (index >= gLocalMods.entryCount) { break; } + struct Mod* mod = gLocalMods.entries[index]; - djui_base_set_enabled(node->base, entry->selectable); + djui_base_set_enabled(node->base, mod->selectable); // iterate index++; @@ -86,7 +88,7 @@ static void djui_panel_host_mods_destroy(struct DjuiBase* base) { void djui_panel_host_mods_create(struct DjuiBase* caller) { f32 bodyHeight = (416) + 64 * 1 + 16 * 1; - mod_list_update_selectable(); + mods_update_selectable(); struct DjuiBase* defaultBase = NULL; struct DjuiThreePanel* panel = djui_panel_menu_create(bodyHeight, "\\#ff0800\\M\\#1be700\\O\\#00b3ff\\D\\#ffef00\\S"); @@ -95,13 +97,13 @@ void djui_panel_host_mods_create(struct DjuiBase* caller) { struct DjuiPaginated* paginated = djui_paginated_create(&body->base, 8); sModLayout = paginated->layout; struct DjuiBase* layoutBase = &paginated->layout->base; - for (int i = 0; i < gModTableLocal.entryCount; i++) { - struct ModListEntry* entry = &gModTableLocal.entries[i]; - struct DjuiCheckbox* checkbox = djui_checkbox_create(layoutBase, entry->displayName ? entry->displayName : entry->name, &entry->enabled); + for (int i = 0; i < gLocalMods.entryCount; i++) { + struct Mod* mod = gLocalMods.entries[i]; + struct DjuiCheckbox* checkbox = djui_checkbox_create(layoutBase, mod->name, &mod->enabled); checkbox->base.tag = i; djui_base_set_size_type(&checkbox->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); djui_base_set_size(&checkbox->base, 1.0f, 32); - djui_base_set_enabled(&checkbox->base, entry->selectable); + djui_base_set_enabled(&checkbox->base, mod->selectable); djui_interactable_hook_hover(&checkbox->base, djui_mod_checkbox_on_hover, djui_mod_checkbox_on_hover_end); djui_interactable_hook_value_change(&checkbox->base, djui_mod_checkbox_on_value_change); if (i == 0) { defaultBase = &checkbox->base; } diff --git a/src/pc/mods/mod.c b/src/pc/mods/mod.c index 680c7c62..688dbc2c 100644 --- a/src/pc/mods/mod.c +++ b/src/pc/mods/mod.c @@ -7,7 +7,10 @@ void mod_clear(struct Mod* mod) { for (int j = 0; j < mod->fileCount; j++) { struct ModFile* file = &mod->files[j]; - file = file; + if (file->fp != NULL) { + fclose(file->fp); + file->fp = NULL; + } } if (mod->name != NULL) { @@ -30,6 +33,8 @@ void mod_clear(struct Mod* mod) { } mod->fileCount = 0; + mod->size = 0; + free(mod); } static struct ModFile* mod_allocate_file(struct Mod* mod, char* relativePath) { @@ -51,6 +56,28 @@ static struct ModFile* mod_allocate_file(struct Mod* mod, char* relativePath) { return NULL; } + // figure out full path + char fullPath[SYS_MAX_PATH] = { 0 }; + if (!mod_file_full_path(fullPath, mod, file)) { + LOG_ERROR("Failed to concat path: '%s' + '%s'", mod->basePath, relativePath); + return NULL; + } + + // open file + FILE* f = fopen(fullPath, "rb"); + if (f == NULL) { + LOG_ERROR("Failed to open '%s'", fullPath); + return NULL; + } + + // get size + fseek(f, 0, SEEK_END); + file->size = ftell(f); + mod->size += file->size; + + // close file + fclose(f); + return file; } @@ -112,7 +139,7 @@ static bool mod_load_files(struct Mod* mod, char* modName, char* fullPath) { while ((dir = readdir(d)) != NULL) { // sanity check / fill path[] if (!directory_sanity_check(dir, actorsPath, path)) { continue; } - if (snprintf(relativePath, SYS_MAX_PATH - 1, "%s/actors/%s", modName, dir->d_name) < 0) { + if (snprintf(relativePath, SYS_MAX_PATH - 1, "actors/%s", dir->d_name) < 0) { LOG_ERROR("Could not concat actor path!"); return false; } @@ -229,23 +256,28 @@ bool mod_load(struct Mods* mods, char* basePath, char* modName) { } // make sure mod is unique - for (int i = 0; i < mods->modCount; i++) { - struct Mod* compareMod = &mods->entries[i]; + for (int i = 0; i < mods->entryCount; i++) { + struct Mod* compareMod = mods->entries[i]; if (!strcmp(compareMod->relativePath, modName)) { return true; } } // allocate mod - u16 modIndex = mods->modCount++; - mods->entries = realloc(mods->entries, sizeof(struct Mod) * mods->modCount); + u16 modIndex = mods->entryCount++; + mods->entries = realloc(mods->entries, sizeof(struct Mod*) * mods->entryCount); if (mods->entries == NULL) { LOG_ERROR("Failed to allocate entries!"); mods_clear(mods); return false; } - struct Mod* mod = &mods->entries[modIndex]; - memset(mod, 0, sizeof(struct Mod)); + mods->entries[modIndex] = calloc(1, sizeof(struct Mod)); + struct Mod* mod = mods->entries[modIndex]; + if (mod == NULL) { + LOG_ERROR("Failed to allocate mod!"); + mods_clear(mods); + return false; + } // set paths char* cpyPath = isDirectory ? fullPath : basePath; diff --git a/src/pc/mods/mod.h b/src/pc/mods/mod.h index bc292e76..d6a902e6 100644 --- a/src/pc/mods/mod.h +++ b/src/pc/mods/mod.h @@ -9,6 +9,8 @@ struct Mods; struct ModFile { char relativePath[SYS_MAX_PATH]; + size_t size; + FILE* fp; }; struct Mod { @@ -21,6 +23,8 @@ struct Mod { u16 fileCount; bool isDirectory; bool enabled; + bool selectable; + size_t size; }; void mod_clear(struct Mod* mod); diff --git a/src/pc/mods/mods.c b/src/pc/mods/mods.c index 9ce4bec4..5182cb24 100644 --- a/src/pc/mods/mods.c +++ b/src/pc/mods/mods.c @@ -5,19 +5,55 @@ #define MOD_DIRECTORY "mods" -static struct Mods gLocalMods = { 0 }; +struct Mods gLocalMods = { 0 }; +struct Mods gRemoteMods = { 0 }; +struct Mods gActiveMods = { 0 }; -void mods_clear(struct Mods* mods) { - for (int i = 0; i < mods->modCount; i ++) { - struct Mod* mod = &mods->entries[i]; - mod_clear(mod); +void mods_activate(struct Mods* mods) { + mods_clear(&gActiveMods); + + // count enabled + u16 enabledCount = 0; + for (int i = 0; i < mods->entryCount; i++) { + struct Mod* mod = mods->entries[i]; + if (mod->enabled) { enabledCount++; } } - if (mods->entries != NULL) { - free(mods->entries); - mods->entries = NULL; + // allocate + gActiveMods.entries = calloc(enabledCount, sizeof(struct Mod*)); + if (gActiveMods.entries == NULL) { + LOG_ERROR("Failed to allocate active mods table!"); + return; + } + + // copy enabled entries + for (int i = 0; i < mods->entryCount; i++) { + struct Mod* mod = mods->entries[i]; + if (mod->enabled) { + gActiveMods.entries[gActiveMods.entryCount++] = mod; + } + } + + // open file pointers + for (int i = 0; i < gActiveMods.entryCount; i++) { + struct Mod* mod = gActiveMods.entries[i]; + for (int j = 0; j < mod->fileCount; j++) { + struct ModFile* file = &mod->files[j]; + + char fullPath[SYS_MAX_PATH] = { 0 }; + if (!mod_file_full_path(fullPath, mod, file)) { + LOG_ERROR("Failed to concat path: '%s' + '%s'", mod->basePath, relativePath); + continue; + } + + file->fp = fopen(fullPath, "rb"); + if (file->fp == NULL) { + LOG_ERROR("Failed to open file '%s'", fullPath); + continue; + } + + } } - mods->modCount = 0; } static void mods_load(struct Mods* mods, char* modsBasePath) { @@ -78,8 +114,50 @@ void mods_init(void) { // load mods if (hasUserPath) { mods_load(&gLocalMods, userModPath); } mods_load(&gLocalMods, "./" MOD_DIRECTORY); + + // calculate total size + gLocalMods.size = 0; + for (int i = 0; i < gLocalMods.entryCount; i++) { + struct Mod* mod = gLocalMods.entries[i]; + gLocalMods.size += mod->size; + } +} + +void mods_clear(struct Mods* mods) { + if (mods == &gActiveMods) { + // don't clear the mods of gActiveMods since they're a copy + // just close all file pointers + for (int i = 0; i < mods->entryCount; i ++) { + struct Mod* mod = mods->entries[i]; + for (int j = 0; j < mod->fileCount; j++) { + struct ModFile* file = &mod->files[j]; + if (file->fp != NULL) { + fclose(file->fp); + file->fp = NULL; + } + } + } + } else { + // clear mods of gLocalMods and gRemoteMods + for (int i = 0; i < mods->entryCount; i ++) { + struct Mod* mod = mods->entries[i]; + mod_clear(mod); + } + } + + // cleanup entries + if (mods->entries != NULL) { + free(mods->entries); + mods->entries = NULL; + } + + // cleanup params + mods->entryCount = 0; + mods->size = 0; } void mods_shutdown(void) { + mods_clear(&gRemoteMods); + mods_clear(&gActiveMods); mods_clear(&gLocalMods); } diff --git a/src/pc/mods/mods.h b/src/pc/mods/mods.h index 36f4d9f4..2835944b 100644 --- a/src/pc/mods/mods.h +++ b/src/pc/mods/mods.h @@ -6,11 +6,18 @@ #include "src/pc/platform.h" #include "mod.h" +#define MAX_MOD_SIZE (2 * 1048576) // 2MB + struct Mods { - struct Mod* entries; - u16 modCount; + struct Mod** entries; + u16 entryCount; + size_t size; }; +extern struct Mods gLocalMods; +extern struct Mods gRemoteMods; +extern struct Mods gActiveMods; + void mods_clear(struct Mods* mods); void mods_init(void); void mods_shutdown(void); diff --git a/src/pc/mods/mods_utils.c b/src/pc/mods/mods_utils.c index d35c7aa6..7cf01c1a 100644 --- a/src/pc/mods/mods_utils.c +++ b/src/pc/mods/mods_utils.c @@ -1,8 +1,78 @@ #include #include +#include "mods.h" #include "mods_utils.h" #include "pc/debuglog.h" +void mods_size_enforce(void) { + for (int i = 0; i < gLocalMods.entryCount; i++) { + struct Mod* mod = gLocalMods.entries[i]; + if (mod->size >= MAX_MOD_SIZE) { + mod->enabled = false; + mod->selectable = false; + } + } +} + +static bool mods_incompatible_match(struct Mod* a, struct Mod* b) { + if (a->incompatible == NULL || b->incompatible == NULL) { + return false; + } + if (strlen(a->incompatible) == 0 || strlen(b->incompatible) == 0) { + return false; + } + + char* ai = a->incompatible; + char* bi = b->incompatible; + char* atoken = NULL; + char* btoken = NULL; + char* arest = NULL; + char* brest = NULL; + + for (atoken = strtok_r(ai, " ", &arest); atoken != NULL; atoken = strtok_r(NULL, " ", &arest)) { + for (btoken = strtok_r(bi, " ", &brest); btoken != NULL; btoken = strtok_r(NULL, " ", &brest)) { + if (!strcmp(atoken, btoken)) { + return true; + } + } + } + + return false; +} + +void mods_update_selectable(void) { + // reset selectable value + for (int i = 0; i < gLocalMods.entryCount; i++) { + struct Mod* mod = gLocalMods.entries[i]; + mod->selectable = true; + } + + // figure out which ones to deselect + for (int i = 0; i < gLocalMods.entryCount; i++) { + struct Mod* mod = gLocalMods.entries[i]; + if (mod->enabled) { continue; } + + for (int j = 0; j < gLocalMods.entryCount; j++) { + if (j == i) { continue; } + struct Mod* mod2 = gLocalMods.entries[j]; + if (!mod2->enabled) { continue; } + + if (mods_incompatible_match(mod, mod2)) { + mod->selectable = false; + break; + } + } + } + + mods_size_enforce(); +} + +bool mod_file_full_path(char* destination, struct Mod* mod, struct ModFile* modFile) { + return concat_path(destination, mod->basePath, modFile->relativePath); +} + +////////////////////////////////////////////////////////////////////////////////////////// + bool str_ends_with(char* string, char* suffix) { if (string == NULL || suffix == NULL) { return false; } @@ -14,6 +84,7 @@ bool str_ends_with(char* string, char* suffix) { return !strcmp(&string[stringLength - suffixLength], suffix); } +////////////////////////////////////////////////////////////////////////////////////////// char* extract_lua_field(char* fieldName, char* buffer) { size_t length = strlen(fieldName); @@ -25,6 +96,8 @@ char* extract_lua_field(char* fieldName, char* buffer) { return NULL; } +////////////////////////////////////////////////////////////////////////////////////////// + bool path_exists(char* path) { struct stat sb = { 0 }; return (stat(path, &sb) == 0); diff --git a/src/pc/mods/mods_utils.h b/src/pc/mods/mods_utils.h index 33449b50..26d085c8 100644 --- a/src/pc/mods/mods_utils.h +++ b/src/pc/mods/mods_utils.h @@ -5,6 +5,10 @@ #include #include "src/pc/platform.h" +void mods_size_enforce(void); +void mods_update_selectable(void); +bool mod_file_full_path(char* destination, struct Mod* mod, struct ModFile* modFile); + bool str_ends_with(char* string, char* suffix); char* extract_lua_field(char* fieldName, char* buffer); diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index 8faeb2e4..a098d42b 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -49,7 +49,6 @@ #include "pc/network/network_player.h" #include "pc/djui/djui.h" -#include "pc/mod_list.h" #include "pc/mods/mods.h" OSMesg D_80339BEC; @@ -171,7 +170,6 @@ void game_deinit(void) { gfx_shutdown(); network_shutdown(true); smlua_shutdown(); - mod_list_shutdown(); mods_shutdown(); inited = false; } @@ -222,7 +220,6 @@ void main_func(void) { const char *userpath = gCLIOpts.SavePath[0] ? gCLIOpts.SavePath : sys_user_path(); fs_init(sys_ropaths, gamedir, userpath); - mod_list_init(); mods_init(); configfile_load(configfile_name()); if (configPlayerModel >= CT_MAX) { configPlayerModel = 0; }