Path: blob/master/RSDKv5/RSDK/Core/ModAPI.cpp
1162 views
#include "RSDK/Core/RetroEngine.hpp"12#if RETRO_USE_MOD_LOADER34using namespace RSDK;56#if RETRO_REV0U7#include "Legacy/ModAPILegacy.cpp"8#endif910#include <filesystem>11#include <stdexcept>12#include <functional>1314#if RETRO_PLATFORM != RETRO_ANDROID15namespace fs = std::filesystem;16#else17bool fs::exists(fs::path path)18{19auto *jni = GetJNISetup();20jbyteArray array = jni->env->NewByteArray(path.string().length());21jni->env->SetByteArrayRegion(array, 0, path.string().length(), (jbyte *)path.string().c_str());22return jni->env->CallBooleanMethod(jni->thiz, fsExists, array);23}2425bool fs::is_directory(fs::path path)26{27auto *jni = GetJNISetup();28jbyteArray array = jni->env->NewByteArray(path.string().length());29jni->env->SetByteArrayRegion(array, 0, path.string().length(), (jbyte *)path.string().c_str());30return jni->env->CallBooleanMethod(jni->thiz, fsIsDir, array);31}3233fs::path_list fs::directory_iterator(fs::path path)34{35auto *jni = GetJNISetup();36jbyteArray array = jni->env->NewByteArray(path.string().length());37jni->env->SetByteArrayRegion(array, 0, path.string().length(), (jbyte *)path.string().c_str());38return fs::path_list((jobjectArray)jni->env->CallObjectMethod(jni->thiz, fsDirIter, array));39}40#endif4142#include "iniparser/iniparser.h"4344int32 RSDK::currentObjectID = 0;45std::vector<ObjectClass *> allocatedInherits;4647// this helps later on just trust me48#define MODAPI_ENDS_WITH(str) buf.length() > strlen(str) && !buf.compare(buf.length() - strlen(str), strlen(str), std::string(str))4950ModSettings RSDK::modSettings;51std::vector<ModInfo> RSDK::modList;52std::vector<ModCallbackSTD> RSDK::modCallbackList[MODCB_MAX];53std::vector<StateHook> RSDK::stateHookList;54std::vector<ObjectHook> RSDK::objectHookList;55ModVersionInfo RSDK::targetModVersion = { RETRO_REVISION, 0, RETRO_MOD_LOADER_VER };5657char RSDK::customUserFileDir[0x100];5859RSDK::ModInfo *RSDK::currentMod;6061std::vector<RSDK::ModPublicFunctionInfo> gamePublicFuncs;6263void *RSDK::modFunctionTable[RSDK::ModTable_Count];6465std::map<uint32, uint32> RSDK::superLevels;66int32 RSDK::inheritLevel = 0;6768#define ADD_MOD_FUNCTION(id, func) modFunctionTable[id] = (void *)func;6970// https://www.techiedelight.com/trim-string-cpp-remove-leading-trailing-spaces/71std::string trim(const std::string &s)72{73auto start = s.begin();74while (start != s.end() && std::isspace(*start)) {75start++;76}7778auto end = s.end();79do {80end--;81} while (std::distance(start, end) > 0 && std::isspace(*end));8283return std::string(start, end + 1);84}8586void RSDK::InitModAPI(bool32 getVersion)87{88memset(modFunctionTable, 0, sizeof(modFunctionTable));8990// ============================91// Mod Function Table92// ============================9394// Registration & Core95ADD_MOD_FUNCTION(ModTable_RegisterGlobals, ModRegisterGlobalVariables);96ADD_MOD_FUNCTION(ModTable_RegisterObject, ModRegisterObject);97ADD_MOD_FUNCTION(ModTable_RegisterObjectSTD, ModRegisterObject_STD);98ADD_MOD_FUNCTION(ModTable_RegisterObjectHook, ModRegisterObjectHook);99ADD_MOD_FUNCTION(ModTable_FindObject, ModFindObject);100ADD_MOD_FUNCTION(ModTable_GetGlobals, GetGlobals);101ADD_MOD_FUNCTION(ModTable_Super, Super);102103// Mod Info104ADD_MOD_FUNCTION(ModTable_LoadModInfo, LoadModInfo);105ADD_MOD_FUNCTION(ModTable_GetModPath, GetModPath);106ADD_MOD_FUNCTION(ModTable_GetModCount, GetModCount);107ADD_MOD_FUNCTION(ModTable_GetModIDByIndex, GetModIDByIndex);108ADD_MOD_FUNCTION(ModTable_ForeachModID, ForeachModID);109110// Mod Callbacks & Public Functions111ADD_MOD_FUNCTION(ModTable_AddModCallback, AddModCallback);112ADD_MOD_FUNCTION(ModTable_AddModCallbackSTD, AddModCallback_STD);113ADD_MOD_FUNCTION(ModTable_AddPublicFunction, AddPublicFunction);114ADD_MOD_FUNCTION(ModTable_GetPublicFunction, GetPublicFunction);115116// Mod Settings117ADD_MOD_FUNCTION(ModTable_GetSettingsBool, GetSettingsBool);118ADD_MOD_FUNCTION(ModTable_GetSettingsInt, GetSettingsInteger);119ADD_MOD_FUNCTION(ModTable_GetSettingsFloat, GetSettingsFloat);120ADD_MOD_FUNCTION(ModTable_GetSettingsString, GetSettingsString);121ADD_MOD_FUNCTION(ModTable_SetSettingsBool, SetSettingsBool);122ADD_MOD_FUNCTION(ModTable_SetSettingsInt, SetSettingsInteger);123ADD_MOD_FUNCTION(ModTable_SetSettingsFloat, SetSettingsFloat);124ADD_MOD_FUNCTION(ModTable_SetSettingsString, SetSettingsString);125ADD_MOD_FUNCTION(ModTable_SaveSettings, SaveSettings);126127// Config128ADD_MOD_FUNCTION(ModTable_GetConfigBool, GetConfigBool);129ADD_MOD_FUNCTION(ModTable_GetConfigInt, GetConfigInteger);130ADD_MOD_FUNCTION(ModTable_GetConfigFloat, GetConfigFloat);131ADD_MOD_FUNCTION(ModTable_GetConfigString, GetConfigString);132ADD_MOD_FUNCTION(ModTable_ForeachConfig, ForeachConfig);133ADD_MOD_FUNCTION(ModTable_ForeachConfigCategory, ForeachConfigCategory);134135// Achievements136ADD_MOD_FUNCTION(ModTable_RegisterAchievement, RegisterAchievement);137ADD_MOD_FUNCTION(ModTable_GetAchievementInfo, GetAchievementInfo);138ADD_MOD_FUNCTION(ModTable_GetAchievementIndexByID, GetAchievementIndexByID);139ADD_MOD_FUNCTION(ModTable_GetAchievementCount, GetAchievementCount);140141// Shaders142ADD_MOD_FUNCTION(ModTable_LoadShader, RenderDevice::LoadShader);143144// StateMachine145ADD_MOD_FUNCTION(ModTable_StateMachineRun, StateMachineRun);146ADD_MOD_FUNCTION(ModTable_RegisterStateHook, RegisterStateHook);147ADD_MOD_FUNCTION(ModTable_HandleRunState_HighPriority, HandleRunState_HighPriority);148ADD_MOD_FUNCTION(ModTable_HandleRunState_LowPriority, HandleRunState_LowPriority);149150#if RETRO_MOD_LOADER_VER >= 2151// Mod Settings (Part 2)152ADD_MOD_FUNCTION(ModTable_ForeachSetting, ForeachSetting);153ADD_MOD_FUNCTION(ModTable_ForeachSettingCategory, ForeachSettingCategory);154155// Files156ADD_MOD_FUNCTION(ModTable_ExcludeFile, ExcludeFile);157ADD_MOD_FUNCTION(ModTable_ExcludeAllFiles, ExcludeAllFiles);158ADD_MOD_FUNCTION(ModTable_ReloadFile, ReloadFile);159ADD_MOD_FUNCTION(ModTable_ReloadAllFiles, ReloadAllFiles);160161// Graphics162ADD_MOD_FUNCTION(ModTable_GetSpriteAnimation, GetSpriteAnimation);163ADD_MOD_FUNCTION(ModTable_GetSpriteSurface, GetSpriteSurface);164ADD_MOD_FUNCTION(ModTable_GetPaletteBank, GetPaletteBank);165ADD_MOD_FUNCTION(ModTable_GetActivePaletteBuffer, GetActivePaletteBuffer);166ADD_MOD_FUNCTION(ModTable_GetRGB32To16Buffer, GetRGB32To16Buffer);167ADD_MOD_FUNCTION(ModTable_GetBlendLookupTable, GetBlendLookupTable);168ADD_MOD_FUNCTION(ModTable_GetSubtractLookupTable, GetSubtractLookupTable);169ADD_MOD_FUNCTION(ModTable_GetTintLookupTable, GetTintLookupTable);170ADD_MOD_FUNCTION(ModTable_GetMaskColor, GetMaskColor);171ADD_MOD_FUNCTION(ModTable_GetScanEdgeBuffer, GetScanEdgeBuffer);172ADD_MOD_FUNCTION(ModTable_GetCamera, GetCamera);173ADD_MOD_FUNCTION(ModTable_GetShader, GetShader);174ADD_MOD_FUNCTION(ModTable_GetModel, GetModel);175ADD_MOD_FUNCTION(ModTable_GetScene3D, GetScene3D);176ADD_MOD_FUNCTION(ModTable_DrawDynamicAniTile, DrawDynamicAniTile);177178// Audio179ADD_MOD_FUNCTION(ModTable_GetSfx, GetSfxEntry);180ADD_MOD_FUNCTION(ModTable_GetChannel, GetChannel);181182// Objects/Entities183ADD_MOD_FUNCTION(ModTable_GetGroupEntities, GetGroupEntities);184185// Collision186ADD_MOD_FUNCTION(ModTable_SetPathGripSensors, SetPathGripSensors);187ADD_MOD_FUNCTION(ModTable_FloorCollision, FloorCollision);188ADD_MOD_FUNCTION(ModTable_LWallCollision, LWallCollision);189ADD_MOD_FUNCTION(ModTable_RoofCollision, RoofCollision);190ADD_MOD_FUNCTION(ModTable_RWallCollision, RWallCollision);191ADD_MOD_FUNCTION(ModTable_FindFloorPosition, FindFloorPosition);192ADD_MOD_FUNCTION(ModTable_FindLWallPosition, FindLWallPosition);193ADD_MOD_FUNCTION(ModTable_FindRoofPosition, FindRoofPosition);194ADD_MOD_FUNCTION(ModTable_FindRWallPosition, FindRWallPosition);195ADD_MOD_FUNCTION(ModTable_CopyCollisionMask, CopyCollisionMask);196ADD_MOD_FUNCTION(ModTable_GetCollisionInfo, GetCollisionInfo);197#endif198199superLevels.clear();200inheritLevel = 0;201LoadMods(false, getVersion);202}203204void RSDK::SortMods()205{206if (ENGINE_VERSION) {207for (int32 m = 0; m < modList.size(); ++m) {208int32 targetVersion = modList[m].forceVersion ? modList[m].forceVersion : modList[m].targetVersion;209210if (modList[m].active && targetVersion != -1 && targetVersion != ENGINE_VERSION) {211PrintLog(PRINT_NORMAL, "[MOD] Mod %s disabled due to target version mismatch", modList[m].id.c_str());212modList[m].active = false;213}214}215}216217std::stable_sort(modList.begin(), modList.end(), [](const ModInfo &a, const ModInfo &b) {218if (!(a.active && b.active))219return a.active;220// keep it unsorted i guess221return false;222});223}224225void RSDK::LoadModSettings()226{227customUserFileDir[0] = 0;228229modSettings.redirectSaveRAM = false;230modSettings.disableGameLogic = false;231232#if RETRO_REV0U233modSettings.versionOverride = 0;234modSettings.forceScripts = customSettings.forceScripts;235#endif236237if (modList.empty())238return;239240// Iterate backwards to find the last active mod in the list241int32 start = modList.size() - 1;242while ((start != -1) && !modList[start].active) {243--start;244}245246// No active mod in the list247if (start == -1)248return;249250for (int32 i = start; i >= 0; --i) {251ModInfo *mod = &modList[i];252253if (mod->redirectSaveRAM) {254if (SKU::userFileDir[0])255sprintf(customUserFileDir, "%smods/%s/", SKU::userFileDir, mod->folderName.c_str());256else257sprintf(customUserFileDir, "mods/%s/", mod->folderName.c_str());258}259260modSettings.redirectSaveRAM |= mod->redirectSaveRAM ? 1 : 0;261modSettings.disableGameLogic |= mod->disableGameLogic ? 1 : 0;262263#if RETRO_REV0U264if (mod->forceVersion)265modSettings.versionOverride = mod->forceVersion;266modSettings.forceScripts |= mod->forceScripts ? 1 : 0;267#endif268}269}270271void RSDK::ApplyModChanges()272{273#if RETRO_REV0U274uint32 category = sceneInfo.activeCategory;275uint32 scene = sceneInfo.listPos;276dataStorage[DATASET_SFX].usedStorage = 0;277RefreshModFolders(true);278LoadModSettings();279DetectEngineVersion();280if (!engine.version)281engine.version = devMenu.startingVersion;282283switch (engine.version) {284case 5:285globalVarsInitCB = NULL;286LoadGameConfig();287sceneInfo.state = ENGINESTATE_DEVMENU;288Legacy::gameMode = Legacy::ENGINE_MAINGAME;289break;290291case 4:292Legacy::v4::LoadGameConfig("Data/Game/GameConfig.bin");293strcpy(gameVerInfo.version, "Legacy v4 Mode");294295sceneInfo.state = ENGINESTATE_NONE; // i think this is fine ??? lmk if otherwise // rmg seal of approval // WAIT THIS WAS ME296Legacy::gameMode = Legacy::ENGINE_DEVMENU;297break;298299case 3:300Legacy::v3::LoadGameConfig("Data/Game/GameConfig.bin");301strcpy(gameVerInfo.version, "Legacy v3 Mode");302303sceneInfo.state = ENGINESTATE_NONE;304Legacy::gameMode = Legacy::ENGINE_DEVMENU;305break;306}307if (engine.version == devMenu.startingVersion) {308sceneInfo.activeCategory = category;309sceneInfo.listPos = scene;310}311#else312uint32 category = sceneInfo.activeCategory;313uint32 scene = sceneInfo.listPos;314dataStorage[DATASET_SFX].usedStorage = 0;315RefreshModFolders(true);316LoadModSettings();317LoadGameConfig();318sceneInfo.activeCategory = category;319sceneInfo.listPos = scene;320#endif321RenderDevice::SetWindowTitle();322}323324void DrawStatus(const char *str)325{326int32 dy = currentScreen->center.y - 32;327DrawRectangle(currentScreen->center.x - 128, dy + 52, 0x100, 0x8, 0x80, 0xFF, INK_NONE, true);328DrawDevString(str, currentScreen->center.x, dy + 52, ALIGN_CENTER, 0xFFFFFF);329330RenderDevice::CopyFrameBuffer();331RenderDevice::FlipScreen();332}333334#if RETRO_RENDERDEVICE_EGL335// egl devices are slower in I/O so render more increments336#define BAR_THRESHOLD (10.F)337#define RENDER_COUNT (200)338#else339#define BAR_THRESHOLD (100.F)340#define RENDER_COUNT (200)341#endif342343bool32 RSDK::ScanModFolder(ModInfo *info, const char *targetFile, bool32 fromLoadMod, bool32 loadingBar)344{345if (!info)346return false;347348const std::string modDir = info->path;349350if (!targetFile)351info->fileMap.clear();352353std::string targetFileStr = "";354if (targetFile) {355char pathLower[0x100];356memset(pathLower, 0, sizeof(char) * 0x100);357for (int32 c = 0; c < strlen(targetFile); ++c) pathLower[c] = tolower(targetFile[c]);358359targetFileStr = std::string(pathLower);360}361362fs::path dataPath(modDir);363int32 dy = currentScreen->center.y - 32;364int32 dx = currentScreen->center.x;365366if (targetFile) {367if (fs::exists(fs::path(modDir + "/" + targetFileStr))) {368info->fileMap.insert(std::pair<std::string, std::string>(targetFileStr, modDir + "/" + targetFileStr));369return true;370}371else372return false;373}374375if (fs::exists(dataPath) && fs::is_directory(dataPath)) {376try {377if (loadingBar) {378currentScreen = &screens[0];379DrawRectangle(dx - 0x80 + 0x10, dy + 48, 0x100 - 0x20, 0x10, 0x000000, 0xFF, INK_NONE, true);380DrawDevString(fromLoadMod ? "Getting count..." : ("Scanning " + info->id + "...").c_str(), currentScreen->center.x, dy + 52,381ALIGN_CENTER, 0xFFFFFF);382RenderDevice::CopyFrameBuffer();383RenderDevice::FlipScreen();384}385386auto dirIterator = fs::recursive_directory_iterator(dataPath, fs::directory_options::follow_directory_symlink);387388std::vector<fs::directory_entry> files;389390int32 renders = 1;391int32 size = 0;392393for (auto dirFile : dirIterator) {394#if RETRO_PLATFORM != RETRO_ANDROID395if (!dirFile.is_directory()) {396#endif397files.push_back(dirFile);398399if (loadingBar && ++size >= RENDER_COUNT * renders) {400DrawRectangle(dx - 0x80 + 0x10, dy + 48, 0x100 - 0x20, 0x10, 0x000000, 0xFF, INK_NONE, true);401DrawDevString((std::to_string(size) + " files").c_str(), currentScreen->center.x, dy + 52, ALIGN_CENTER, 0xFFFFFF);402RenderDevice::CopyFrameBuffer();403RenderDevice::FlipScreen();404renders++;405}406#if RETRO_PLATFORM != RETRO_ANDROID407}408#endif409}410411int32 i = 0;412int32 bars = 1;413414for (auto dirFile : files) {415std::string folderPath = dirFile.path().string().substr(dataPath.string().length() + 1);416std::transform(folderPath.begin(), folderPath.end(), folderPath.begin(),417[](unsigned char c) { return c == '\\' ? '/' : std::tolower(c); });418419info->fileMap.insert(std::pair<std::string, std::string>(folderPath, dirFile.path().string()));420if (loadingBar && (size * bars) / BAR_THRESHOLD < ++i) {421DrawRectangle(dx - 0x80 + 0x10, dy + 48, 0x100 - 0x20, 0x10, 0x000000, 0xFF, INK_NONE, true);422DrawRectangle(dx - 0x80 + 0x10 + 2, dy + 50, (int32)((0x100 - 0x20 - 4) * (i / (float)size)), 0x10 - 4, 0x00FF00, 0xFF, INK_NONE,423true);424while ((size * bars) / BAR_THRESHOLD < i) bars++;425DrawDevString((std::to_string(i) + "/" + std::to_string(size)).c_str(), currentScreen->center.x, dy + 52, ALIGN_CENTER, 0xFFFFFF);426RenderDevice::CopyFrameBuffer();427RenderDevice::FlipScreen();428}429}430} catch (fs::filesystem_error &fe) {431PrintLog(PRINT_ERROR, "Mod File Scanning Error: %s", fe.what());432}433}434435if (loadingBar && fromLoadMod) {436DrawRectangle(dx - 0x80 + 0x10, dy + 48, 0x100 - 0x20, 0x10, 0x000080, 0xFF, INK_NONE, true);437438RenderDevice::CopyFrameBuffer();439RenderDevice::FlipScreen();440}441442return true;443}444445void RSDK::UnloadMods()446{447for (ModInfo &mod : modList) {448if (mod.unloadMod)449mod.unloadMod();450451for (Link::Handle &handle : mod.modLogicHandles) {452Link::Close(handle);453}454455mod.modLogicHandles.clear();456}457458modList.clear();459for (int32 c = 0; c < MODCB_MAX; ++c) modCallbackList[c].clear();460stateHookList.clear();461objectHookList.clear();462463for (int32 i = 0; i < (int32)allocatedInherits.size(); ++i) {464ObjectClass *inherit = allocatedInherits[i];465if (inherit)466delete inherit;467}468allocatedInherits.clear();469470#if RETRO_REV0U471memset(Legacy::modTypeNames, 0, sizeof(Legacy::modTypeNames));472memset(Legacy::modTypeNames, 0, sizeof(Legacy::modScriptPaths));473memset(Legacy::modScriptFlags, 0, sizeof(Legacy::modScriptFlags));474Legacy::modObjCount = 0;475476memset(modSettings.playerNames, 0, sizeof(modSettings.playerNames));477modSettings.playerCount = 0;478479modSettings.versionOverride = 0;480modSettings.activeMod = -1;481#endif482483customUserFileDir[0] = 0;484485// Clear storage486dataStorage[DATASET_STG].usedStorage = 0;487DefragmentAndGarbageCollectStorage(DATASET_MUS);488dataStorage[DATASET_SFX].usedStorage = 0;489dataStorage[DATASET_STR].usedStorage = 0;490dataStorage[DATASET_TMP].usedStorage = 0;491492#if RETRO_REV02493// Clear out any userDBs494if (SKU::userDBStorage)495SKU::userDBStorage->ClearAllUserDBs();496#endif497}498499void RSDK::LoadMods(bool newOnly, bool32 getVersion)500{501if (!newOnly) {502UnloadMods();503504if (AudioDevice::initializedAudioChannels) {505// Stop all sounds506for (int32 c = 0; c < CHANNEL_COUNT; ++c) StopChannel(c);507508// we're about to reload these, so clear anything we already have509ClearGlobalSfx();510}511}512513using namespace std;514char modBuf[0x100];515sprintf_s(modBuf, sizeof(modBuf), "%smods", SKU::userFileDir);516fs::path modPath(modBuf);517518if (fs::exists(modPath) && fs::is_directory(modPath)) {519string mod_config = modPath.string() + "/modconfig.ini";520FileIO *configFile = fOpen(mod_config.c_str(), "r");521if (configFile) {522fClose(configFile);523auto ini = iniparser_load(mod_config.c_str());524525int32 c = iniparser_getsecnkeys(ini, "Mods");526const char **keys = new const char *[c];527iniparser_getseckeys(ini, "Mods", keys);528529for (int32 m = 0; m < c; ++m) {530if (newOnly && std::find_if(modList.begin(), modList.end(), [&keys, &m](ModInfo mod) {531return mod.folderName == string(keys[m] + 5);532}) != modList.end())533continue;534ModInfo info = {};535bool32 active = iniparser_getboolean(ini, keys[m], false);536bool32 loaded = LoadMod(&info, modPath.string(), string(keys[m] + 5), active, getVersion);537if (info.id.empty()) {538PrintLog(PRINT_NORMAL, "[MOD] Mod %s doesn't exist!", keys[m] + 5);539continue;540}541else if (!loaded) {542PrintLog(PRINT_NORMAL, "[MOD] Failed to load mod %s.", info.id.c_str(), active ? "Y" : "N");543info.active = false;544}545else546PrintLog(PRINT_NORMAL, "[MOD] Loaded mod %s! Active: %s", info.id.c_str(), active ? "Y" : "N");547modList.push_back(info);548}549delete[] keys;550iniparser_freedict(ini);551}552553try {554auto rdi = fs::directory_iterator(modPath);555for (auto de : rdi) {556if (de.is_directory()) {557fs::path modDirPath = de.path();558ModInfo info = {};559std::string folder = modDirPath.filename().string();560561if (std::find_if(modList.begin(), modList.end(), [&folder](ModInfo m) { return m.folderName == folder; }) == modList.end()) {562563const std::string modDir = modPath.string() + "/" + folder;564565FileIO *f = fOpen((modDir + "/mod.ini").c_str(), "r");566if (f) {567fClose(f);568LoadMod(&info, modPath.string(), folder, false, getVersion);569modList.push_back(info);570}571}572}573}574} catch (fs::filesystem_error &fe) {575PrintLog(PRINT_ERROR, "Mods folder scanning error: %s", fe.what());576}577}578579int32 dy = currentScreen->center.y - 32;580DrawRectangle(currentScreen->center.x - 128, dy, 0x100, 0x48, 0x80, 0xFF, INK_NONE, true);581DrawDevString("Mod loading done!", currentScreen->center.x, dy + 28, ALIGN_CENTER, 0xFFFFFF);582RenderDevice::CopyFrameBuffer();583RenderDevice::FlipScreen();584585SortMods();586LoadModSettings();587}588589void loadCfg(ModInfo *info, const std::string &path)590{591FileInfo cfg;592InitFileInfo(&cfg);593cfg.externalFile = true;594// CFG FILE READ595if (LoadFile(&cfg, path.c_str(), FMODE_RB)) {596int32 catCount = ReadInt8(&cfg);597for (int32 c = 0; c < catCount; ++c) {598char catBuf[0x100];599ReadString(&cfg, catBuf);600int32 keyCount = ReadInt8(&cfg);601for (int32 k = 0; k < keyCount; ++k) {602// ReadString except w packing the type bit603uint8 size = ReadInt8(&cfg);604char *keyBuf = new char[size & 0x7F];605ReadBytes(&cfg, keyBuf, size & 0x7F);606keyBuf[size & 0x7F] = 0;607uint8 type = size & 0x80;608if (!type) {609char buf[0xFFFF];610ReadString(&cfg, buf);611info->config[catBuf][keyBuf] = buf;612}613else614info->config[catBuf][keyBuf] = std::to_string(ReadInt32(&cfg, false));615delete[] keyBuf;616}617}618619CloseFile(&cfg);620}621}622623bool32 RSDK::LoadMod(ModInfo *info, const std::string &modsPath, const std::string &folder, bool32 active, bool32 getVersion)624{625if (!info)626return false;627628ModInfo *cur = currentMod;629630PrintLog(PRINT_NORMAL, "[MOD] Trying to load mod %s...", folder.c_str());631632info->fileMap.clear();633info->excludedFiles.clear();634info->modLogicHandles.clear();635info->name = "";636info->desc = "";637info->author = "";638info->version = "";639info->id = "";640info->active = false;641info->redirectSaveRAM = false;642info->disableGameLogic = false;643644const std::string modDir = modsPath + "/" + folder;645646FileIO *f = fOpen((modDir + "/mod.ini").c_str(), "r");647if (f) {648int32 dy = currentScreen->center.y - 32;649DrawRectangle(currentScreen->center.x - 128, dy, 0x100, 0x48, 0x80, 0xFF, INK_NONE, true);650651DrawDevString("Loading mod", currentScreen->center.x, dy + 16, ALIGN_CENTER, 0xFFFFFF);652DrawDevString((folder + "...").c_str(), currentScreen->center.x, dy + 28, ALIGN_CENTER, 0xFFFFFF);653654DrawStatus("Parsing INI...");655656fClose(f);657auto modIni = iniparser_load((modDir + "/mod.ini").c_str());658659info->path = modDir;660info->folderName = folder;661info->id = iniparser_getstring(modIni, ":ModID", folder.c_str());662info->active = active;663664info->name = iniparser_getstring(modIni, ":Name", "Unnamed Mod");665info->desc = iniparser_getstring(modIni, ":Description", "");666info->author = iniparser_getstring(modIni, ":Author", "Unknown Author");667info->version = iniparser_getstring(modIni, ":Version", "1.0.0");668669info->redirectSaveRAM = iniparser_getboolean(modIni, ":RedirectSaveRAM", false);670info->disableGameLogic = iniparser_getboolean(modIni, ":DisableGameLogic", false);671672info->forceVersion = iniparser_getint(modIni, ":ForceVersion", 0);673if (!info->forceVersion) {674info->targetVersion = iniparser_getint(modIni, ":TargetVersion", 5);675if (info->targetVersion != -1 && ENGINE_VERSION) {676if (info->targetVersion < 3 || info->targetVersion > 5) {677PrintLog(PRINT_NORMAL, "[MOD] Invalid target version. Should be 3, 4, or 5");678return false;679}680else if (info->targetVersion != ENGINE_VERSION) {681PrintLog(PRINT_NORMAL, "[MOD] Target version does not match current engine version.");682return false;683}684}685}686else687info->targetVersion = info->forceVersion;688info->forceScripts = iniparser_getboolean(modIni, ":TxtScripts", false);689690if (!active) {691iniparser_freedict(modIni);692return true;693}694695// ASSETS696DrawStatus("Scanning mod folder...");697ScanModFolder(info, getVersion ? "Data/Game/GameConfig.bin" : nullptr, true);698699if (!getVersion) {700// LOGIC701std::string logic(iniparser_getstring(modIni, ":LogicFile", ""));702if (logic.length()) {703std::istringstream stream(logic);704std::string buf;705while (std::getline(stream, buf, ',')) {706buf = trim(buf);707DrawStatus(("Starting logic " + buf + "...").c_str());708bool linked = false;709710fs::path file(modDir + "/" + buf);711Link::Handle linkHandle = Link::Open(file.string().c_str());712713if (linkHandle) {714modLink linkModLogic = (modLink)Link::GetSymbol(linkHandle, "LinkModLogic");715const ModVersionInfo *modInfo = (const ModVersionInfo *)Link::GetSymbol(linkHandle, "modInfo");716if (!modInfo) {717// PrintLog(PRINT_NORMAL, "[MOD] Failed to load mod %s...", folder.c_str());718PrintLog(PRINT_NORMAL, "[MOD] ERROR: Failed to find modInfo", file.string().c_str());719720iniparser_freedict(modIni);721currentMod = cur;722return false;723}724725if (modInfo->engineVer != targetModVersion.engineVer) {726// PrintLog(PRINT_NORMAL, "[MOD] Failed to load mod %s...", folder.c_str());727PrintLog(PRINT_NORMAL, "[MOD] ERROR: Logic file '%s' engineVer %d does not match expected engineVer of %d",728file.string().c_str(), modInfo->engineVer, targetModVersion.engineVer);729730iniparser_freedict(modIni);731currentMod = cur;732return false;733}734735if (modInfo->modLoaderVer != targetModVersion.modLoaderVer) {736// PrintLog(PRINT_NORMAL, "[MOD] Failed to load mod %s...", folder.c_str());737PrintLog(PRINT_NORMAL, "[MOD] ERROR: Logic file '%s' modLoaderVer %d does not match expected modLoaderVer of %d",738file.string().c_str(), modInfo->modLoaderVer, targetModVersion.modLoaderVer);739}740741if (linkModLogic) {742info->linkModLogic.push_back(linkModLogic);743linked = true;744}745else {746PrintLog(PRINT_ERROR, "[MOD] ERROR: Failed to find 'LinkModLogic' -> %s", Link::GetError());747}748info->unloadMod = (void (*)())Link::GetSymbol(linkHandle, "UnloadMod");749info->modLogicHandles.push_back(linkHandle);750}751else {752PrintLog(PRINT_ERROR, "[MOD] ERROR: Failed to open mod logic file -> %s", Link::GetError());753}754755if (!linked) {756// PrintLog(PRINT_NORMAL, "[MOD] Failed to load mod %s...", folder.c_str());757PrintLog(PRINT_NORMAL, "[MOD] ERROR: Failed to link logic '%s'", file.string().c_str());758759iniparser_freedict(modIni);760currentMod = cur;761return false;762}763}764}765766// SETTINGS767FileIO *set = fOpen((modDir + "/modSettings.ini").c_str(), "r");768if (set) {769DrawStatus("Reading settings...");770771fClose(set);772using namespace std;773auto modSettingsIni = iniparser_load((modDir + "/modSettings.ini").c_str());774int32 sec = iniparser_getnsec(modSettingsIni);775if (sec) {776for (int32 i = 0; i < sec; ++i) {777const char *secn = iniparser_getsecname(modSettingsIni, i);778int32 len = iniparser_getsecnkeys(modSettingsIni, secn);779const char **keys = new const char *[len];780iniparser_getseckeys(modSettingsIni, secn, keys);781map<string, string> secset;782for (int32 j = 0; j < len; ++j)783secset.insert(pair<string, string>(keys[j] + strlen(secn) + 1, iniparser_getstring(modSettingsIni, keys[j], "")));784info->settings.insert(pair<string, map<string, string>>(secn, secset));785delete[] keys;786}787}788else {789// either you use categories or you don't, i don't make the rules790map<string, string> secset;791for (int32 j = 0; j < modSettingsIni->n; ++j)792secset.insert(pair<string, string>(modSettingsIni->key[j] + 1, modSettingsIni->val[j]));793info->settings.insert(pair<string, map<string, string>>("", secset));794}795iniparser_freedict(modSettingsIni);796}797// CONFIG798loadCfg(info, modDir + "/modConfig.cfg");799800std::string cfg(iniparser_getstring(modIni, ":ConfigFile", ""));801bool saveCfg = false;802if (cfg.length() && info->active) {803std::istringstream stream(cfg);804std::string buf;805while (std::getline(stream, buf, ',')) {806buf = trim(buf);807DrawStatus(("Reading config " + buf + "...").c_str());808809int32 mode = 0;810fs::path file;811if (MODAPI_ENDS_WITH(".ini")) {812file = fs::path(modDir + "/" + buf + ".ini");813mode = 1;814}815else if (MODAPI_ENDS_WITH(".cfg")) {816file = fs::path(modDir + "/" + buf + ".cfg");817mode = 2;818}819820if (!mode) {821file = fs::path(modDir + "/" + buf + ".ini");822if (fs::exists(file))823mode = 1;824}825if (!mode) {826file = fs::path(modDir + "/" + buf + ".cfg");827if (fs::exists(file))828mode = 2;829}830831// if fail just free do nothing832if (!mode)833continue;834835if (mode == 1) {836FileIO *set = fOpen(file.string().c_str(), "r");837if (set) {838saveCfg = true;839fClose(set);840using namespace std;841auto cfgIni = iniparser_load(file.string().c_str());842int32 sec = iniparser_getnsec(cfgIni);843for (int32 i = 0; i < sec; ++i) {844const char *secn = iniparser_getsecname(cfgIni, i);845int32 len = iniparser_getsecnkeys(cfgIni, secn);846const char **keys = new const char *[len];847iniparser_getseckeys(cfgIni, secn, keys);848for (int32 j = 0; j < len; ++j)849info->config[secn][keys[j] + strlen(secn) + 1] = iniparser_getstring(cfgIni, keys[j], "");850delete[] keys;851}852iniparser_freedict(cfgIni);853}854}855else if (mode == 2)856loadCfg(info, file.string());857}858}859860if (saveCfg && info->config.size()) {861DrawStatus("Saving config...");862FileIO *cfg = fOpen((modDir + "/modConfig.cfg").c_str(), "wb");863uint8 ct = info->config.size();864fWrite(&ct, 1, 1, cfg);865for (auto kv : info->config) {866if (!kv.first.length())867continue; // don't save no-categories868uint8 len = kv.first.length();869fWrite(&len, 1, 1, cfg);870WriteText(cfg, kv.first.c_str());871uint8 kt = kv.second.size();872fWrite(&kt, 1, 1, cfg);873for (auto kkv : kv.second) {874uint8 len = (uint8)(kkv.first.length()) & 0x7F;875bool32 isint = false;876int32 r = 0;877try {878r = std::stoi(kkv.second, nullptr, 0);879isint = true;880len |= 0x80;881} catch (...) {882}883fWrite(&len, 1, 1, cfg);884WriteText(cfg, kkv.first.c_str());885if (isint)886fWrite(&r, sizeof(int32), 1, cfg);887else {888uint8 len = kkv.second.length();889fWrite(&len, 1, 1, cfg);890WriteText(cfg, kkv.second.c_str());891}892}893}894fClose(cfg);895}896}897898iniparser_freedict(modIni);899currentMod = cur;900return true;901}902return false;903}904905void RSDK::SaveMods()906{907ModInfo *cur = currentMod;908char modBuf[0x100];909sprintf_s(modBuf, sizeof(modBuf), "%smods/", SKU::userFileDir);910fs::path modPath(modBuf);911912SortMods();913914PrintLog(PRINT_NORMAL, "[MOD] Saving mods...");915916if (fs::exists(modPath) && fs::is_directory(modPath)) {917std::string mod_config = modPath.string() + "/modconfig.ini";918FileIO *file = fOpen(mod_config.c_str(), "w");919920WriteText(file, "[Mods]\n");921922for (int32 m = 0; m < modList.size(); ++m) {923currentMod = &modList[m];924SaveSettings();925WriteText(file, "%s=%c\n", currentMod->folderName.c_str(), currentMod->active ? 'y' : 'n');926}927fClose(file);928}929currentMod = cur;930}931932void RSDK::RunModCallbacks(int32 callbackID, void *data)933{934if (callbackID < 0 || callbackID >= MODCB_MAX)935return;936937for (auto &c : modCallbackList[callbackID]) {938if (c)939c(data);940}941}942943// Mod API944bool32 RSDK::LoadModInfo(const char *id, String *name, String *description, String *version, bool32 *active)945{946if (!id) { // NULL == "Internal" Logic947if (name)948InitString(name, gameVerInfo.gameTitle, 0);949if (description)950InitString(description, gameVerInfo.gameSubtitle, 0);951if (version)952InitString(version, gameVerInfo.version, 0);953if (active)954*active = true;955956return true;957}958else if (!strlen(id) && currentMod) { // "" == Current Mod959if (name)960InitString(name, currentMod->name.c_str(), 0);961if (description)962InitString(description, currentMod->desc.c_str(), 0);963if (version)964InitString(version, currentMod->version.c_str(), 0);965if (active)966*active = currentMod->active;967968return true;969}970971for (int32 m = 0; m < modList.size(); ++m) {972if (modList[m].id == id) {973if (name)974InitString(name, modList[m].name.c_str(), 0);975if (description)976InitString(description, modList[m].desc.c_str(), 0);977if (version)978InitString(version, modList[m].version.c_str(), 0);979if (active)980*active = modList[m].active;981982return true;983}984}985return false;986}987988int32 RSDK::GetModCount(bool32 active)989{990int32 c = 0;991for (auto &m : modList) {992if (++c && active && !m.active)993return c - 1;994}995return c;996}997998const char *RSDK::GetModIDByIndex(uint32 index)999{1000if (index >= modList.size())1001return NULL;1002return modList[index].id.c_str();1003}10041005bool32 RSDK::ForeachModID(String *id)1006{1007if (!id)1008return false;10091010using namespace std;10111012if (id->chars)1013++foreachStackPtr->id;1014else {1015++foreachStackPtr;1016foreachStackPtr->id = 0;1017}10181019if (foreachStackPtr->id >= modList.size()) {1020foreachStackPtr--;1021return false;1022}1023string set = modList[foreachStackPtr->id].id;1024InitString(id, set.c_str(), 0);1025return true;1026}10271028void RSDK::AddModCallback(int32 callbackID, ModCallback callback) { return AddModCallback_STD(callbackID, callback); }10291030void RSDK::AddModCallback_STD(int32 callbackID, ModCallbackSTD callback)1031{1032if (callbackID < 0 || callbackID >= MODCB_MAX)1033return;10341035modCallbackList[callbackID].push_back(callback);1036}10371038void RSDK::AddPublicFunction(const char *functionName, void *functionPtr)1039{1040if (!currentMod)1041return gamePublicFuncs.push_back({ functionName, functionPtr });1042if (!currentMod->active)1043return;1044currentMod->functionList.push_back({ functionName, functionPtr });1045}10461047void *RSDK::GetPublicFunction(const char *id, const char *functionName)1048{1049if (!id) {1050for (auto &f : gamePublicFuncs) {1051if (f.name == functionName)1052return f.ptr;1053}10541055return NULL;1056}10571058if (!strlen(id) && currentMod)1059id = currentMod->id.c_str();10601061for (ModInfo &m : modList) {1062if (m.active && m.id == id) {1063for (auto &f : m.functionList) {1064if (f.name == functionName)1065return f.ptr;1066}10671068return NULL;1069}1070}10711072return NULL;1073}10741075std::string GetModPath_i(const char *id)1076{1077int32 m;1078for (m = 0; m < modList.size(); ++m) {1079if (modList[m].active && modList[m].id == id)1080break;1081}10821083if (m == modList.size())1084return std::string();10851086return modList[m].path;1087}10881089void RSDK::GetModPath(const char *id, String *result)1090{1091std::string modPath = GetModPath_i(id);10921093if (modPath.empty())1094return;10951096InitString(result, modPath.c_str(), 0);1097}10981099std::string GetModSettingsValue(const char *id, const char *key)1100{1101std::string skey(key);1102if (!strchr(key, ':'))1103skey = std::string(":") + key;11041105std::string cat = skey.substr(0, skey.find(":"));1106std::string rkey = skey.substr(skey.find(":") + 1);11071108for (ModInfo &m : modList) {1109if (m.active && m.id == id) {1110try {1111return m.settings.at(cat).at(rkey);1112} catch (std::out_of_range) {1113return std::string();1114}1115}1116}1117return std::string();1118}11191120bool32 RSDK::GetSettingsBool(const char *id, const char *key, bool32 fallback)1121{1122if (!id) {1123// TODO: allow user to get values from settings.ini?1124}1125else if (!strlen(id)) {1126if (!currentMod)1127return fallback;11281129id = currentMod->id.c_str();1130}11311132std::string v = GetModSettingsValue(id, key);11331134if (!v.length()) {1135if (currentMod->id == id)1136SetSettingsBool(key, fallback);1137return fallback;1138}1139char first = v.at(0);1140if (first == 'y' || first == 'Y' || first == 't' || first == 'T' || (first = GetSettingsInteger(id, key, 0)))1141return true;1142if (first == 'n' || first == 'N' || first == 'f' || first == 'F' || !first)1143return false;1144if (currentMod->id == id)1145SetSettingsBool(key, fallback);1146return fallback;1147}11481149int32 RSDK::GetSettingsInteger(const char *id, const char *key, int32 fallback)1150{1151if (!id) {1152// TODO: allow user to get values from settings.ini?1153}1154else if (!strlen(id)) {1155if (!currentMod)1156return fallback;11571158id = currentMod->id.c_str();1159}11601161std::string v = GetModSettingsValue(id, key);11621163if (!v.length()) {1164if (currentMod->id == id)1165SetSettingsInteger(key, fallback);1166return fallback;1167}1168try {1169return std::stoi(v, nullptr, 0);1170} catch (...) {1171if (currentMod->id == id)1172SetSettingsInteger(key, fallback);1173return fallback;1174}1175}11761177float RSDK::GetSettingsFloat(const char *id, const char *key, float fallback)1178{1179if (!id) {1180// TODO: allow user to get values from settings.ini?1181}1182else if (!strlen(id)) {1183if (!currentMod)1184return fallback;11851186id = currentMod->id.c_str();1187}11881189std::string v = GetModSettingsValue(id, key);11901191if (!v.length()) {1192if (currentMod->id == id)1193SetSettingsFloat(key, fallback);1194return fallback;1195}1196try {1197return std::stof(v, nullptr);1198} catch (...) {1199if (currentMod->id == id)1200SetSettingsFloat(key, fallback);1201return fallback;1202}1203}12041205void RSDK::GetSettingsString(const char *id, const char *key, String *result, const char *fallback)1206{1207if (!id) {1208// TODO: allow user to get values from settings.ini?1209}1210else if (!strlen(id)) {1211if (!currentMod) {1212InitString(result, fallback, 0);1213return;1214}12151216id = currentMod->id.c_str();1217}12181219std::string v = GetModSettingsValue(id, key);1220if (!v.length()) {1221InitString(result, fallback, 0);1222if (currentMod->id == id)1223SetSettingsString(key, result);1224return;1225}1226InitString(result, v.c_str(), 0);1227}12281229std::string GetNidConfigValue(const char *key)1230{1231if (!currentMod || !currentMod->active)1232return std::string();1233std::string skey(key);1234if (!strchr(key, ':'))1235skey = std::string(":") + key;12361237std::string cat = skey.substr(0, skey.find(":"));1238std::string rkey = skey.substr(skey.find(":") + 1);12391240try {1241return currentMod->config.at(cat).at(rkey);1242} catch (std::out_of_range) {1243return std::string();1244}1245return std::string();1246}12471248bool32 RSDK::GetConfigBool(const char *key, bool32 fallback)1249{1250std::string v = GetNidConfigValue(key);1251if (!v.length())1252return fallback;1253char first = v.at(0);1254if (first == 'y' || first == 'Y' || first == 't' || first == 'T' || (first = GetConfigInteger(key, 0)))1255return true;1256if (first == 'n' || first == 'N' || first == 'f' || first == 'F' || !first)1257return false;1258return fallback;1259}12601261int32 RSDK::GetConfigInteger(const char *key, int32 fallback)1262{1263std::string v = GetNidConfigValue(key);1264if (!v.length())1265return fallback;1266try {1267return std::stoi(v, nullptr, 0);1268} catch (...) {1269return fallback;1270}1271}12721273float RSDK::GetConfigFloat(const char *key, float fallback)1274{1275std::string v = GetNidConfigValue(key);1276if (!v.length())1277return fallback;1278try {1279return std::stof(v, nullptr);1280} catch (...) {1281return fallback;1282}1283}12841285void RSDK::GetConfigString(const char *key, String *result, const char *fallback)1286{1287std::string v = GetNidConfigValue(key);1288if (!v.length()) {1289InitString(result, fallback, 0);1290return;1291}1292InitString(result, v.c_str(), 0);1293}12941295bool32 RSDK::ForeachConfigCategory(String *category)1296{1297if (!category || !currentMod)1298return false;12991300using namespace std;1301if (!currentMod->config.size())1302return false;13031304if (category->chars)1305++foreachStackPtr->id;1306else {1307++foreachStackPtr;1308foreachStackPtr->id = 0;1309}1310int32 sid = 0;1311string cat;1312bool32 set = false;1313if (currentMod->config[""].size() && foreachStackPtr->id == sid++) {1314set = true;1315cat = "";1316}1317if (!set) {1318for (pair<string, map<string, string>> kv : currentMod->config) {1319if (!kv.first.length())1320continue;1321if (kv.second.size() && foreachStackPtr->id == sid++) {1322set = true;1323cat = kv.first;1324break;1325}1326}1327}1328if (!set) {1329foreachStackPtr--;1330return false;1331}1332InitString(category, cat.c_str(), 0);1333return true;1334}13351336bool32 RSDK::ForeachConfig(String *config)1337{1338if (!config || !currentMod)1339return false;1340using namespace std;1341if (!currentMod->config.size())1342return false;13431344if (config->chars)1345++foreachStackPtr->id;1346else {1347++foreachStackPtr;1348foreachStackPtr->id = 0;1349}1350int32 sid = 0;1351string key, cat;1352if (currentMod->config[""].size()) {1353for (pair<string, string> pair : currentMod->config[""]) {1354if (foreachStackPtr->id == sid++) {1355cat = "";1356key = pair.first;1357break;1358}1359}1360}1361if (!key.length()) {1362for (pair<string, map<string, string>> kv : currentMod->config) {1363if (!kv.first.length())1364continue;1365for (pair<string, string> pair : kv.second) {1366if (foreachStackPtr->id == sid++) {1367cat = kv.first;1368key = pair.first;1369break;1370}1371}1372}1373}1374if (!key.length()) {1375foreachStackPtr--;1376return false;1377}1378string r = cat + ":" + key;1379InitString(config, r.c_str(), 0);1380return true;1381}13821383#if RETRO_MOD_LOADER_VER >= 21384bool32 RSDK::ForeachSettingCategory(const char *id, String *category)1385{1386if (!id) {1387// TODO: allow user to get values from settings.ini?1388}1389else if (!strlen(id)) {1390if (!currentMod)1391return false;13921393id = currentMod->id.c_str();1394}13951396if (!category)1397return false;13981399int32 m;1400for (m = 0; m < modList.size(); ++m) {1401if (modList[m].active && modList[m].id == id)1402break;1403}14041405if (m == modList.size())1406return false;14071408ModInfo *mod = &modList[m];14091410using namespace std;1411if (!mod->settings.size())1412return false;14131414if (category->chars)1415++foreachStackPtr->id;1416else {1417++foreachStackPtr;1418foreachStackPtr->id = 0;1419}1420int32 sid = 0;1421string cat;1422bool32 set = false;1423if (mod->settings[""].size() && foreachStackPtr->id == sid++) {1424set = true;1425cat = "";1426}1427if (!set) {1428for (pair<string, map<string, string>> kv : mod->settings) {1429if (!kv.first.length())1430continue;1431if (kv.second.size() && foreachStackPtr->id == sid++) {1432set = true;1433cat = kv.first;1434break;1435}1436}1437}1438if (!set) {1439foreachStackPtr--;1440return false;1441}1442InitString(category, cat.c_str(), 0);1443return true;1444}14451446bool32 RSDK::ForeachSetting(const char *id, String *setting)1447{1448if (!id) {1449// TODO: allow user to get values from settings.ini?1450}1451else if (!strlen(id)) {1452if (!currentMod)1453return false;14541455id = currentMod->id.c_str();1456}14571458if (!setting)1459return false;1460using namespace std;1461if (!currentMod->settings.size())1462return false;14631464int32 m;1465for (m = 0; m < modList.size(); ++m) {1466if (modList[m].active && modList[m].id == id)1467break;1468}14691470if (m == modList.size())1471return false;14721473ModInfo *mod = &modList[m];14741475if (setting->chars)1476++foreachStackPtr->id;1477else {1478++foreachStackPtr;1479foreachStackPtr->id = 0;1480}1481int32 sid = 0;1482string key, cat;1483if (mod->settings[""].size()) {1484for (pair<string, string> pair : mod->settings[""]) {1485if (foreachStackPtr->id == sid++) {1486cat = "";1487key = pair.first;1488break;1489}1490}1491}1492if (!key.length()) {1493for (pair<string, map<string, string>> kv : mod->settings) {1494if (!kv.first.length())1495continue;1496for (pair<string, string> pair : kv.second) {1497if (foreachStackPtr->id == sid++) {1498cat = kv.first;1499key = pair.first;1500break;1501}1502}1503}1504}1505if (!key.length()) {1506foreachStackPtr--;1507return false;1508}1509string r = cat + ":" + key;1510InitString(setting, r.c_str(), 0);1511return true;1512}1513#endif15141515void SetModSettingsValue(const char *key, const std::string &val)1516{1517if (!currentMod)1518return;1519std::string skey(key);1520if (!strchr(key, ':'))1521skey = std::string(":") + key;15221523std::string cat = skey.substr(0, skey.find(":"));1524std::string rkey = skey.substr(skey.find(":") + 1);15251526currentMod->settings[cat][rkey] = val;1527}15281529void RSDK::SetSettingsBool(const char *key, bool32 val) { SetModSettingsValue(key, val ? "Y" : "N"); }1530void RSDK::SetSettingsInteger(const char *key, int32 val) { SetModSettingsValue(key, std::to_string(val)); }1531void RSDK::SetSettingsFloat(const char *key, float val) { SetModSettingsValue(key, std::to_string(val)); }1532void RSDK::SetSettingsString(const char *key, String *val)1533{1534char *buf = new char[val->length + 1]; // Take into account '\0'1535GetCString(buf, val);1536SetModSettingsValue(key, buf);1537delete[] buf;1538}15391540void RSDK::SaveSettings()1541{1542using namespace std;1543if (!currentMod || !currentMod->settings.size() || !currentMod->active)1544return;15451546FileIO *file = fOpen((GetModPath_i(currentMod->id.c_str()) + "/modSettings.ini").c_str(), "w");15471548if (currentMod->settings[""].size()) {1549for (pair<string, string> pair : currentMod->settings[""]) WriteText(file, "%s = %s\n", pair.first.c_str(), pair.second.c_str());1550}1551for (pair<string, map<string, string>> kv : currentMod->settings) {1552if (!kv.first.length())1553continue;1554WriteText(file, "\n[%s]\n", kv.first.c_str());1555for (pair<string, string> pair : kv.second) WriteText(file, "%s = %s\n", pair.first.c_str(), pair.second.c_str());1556}1557fClose(file);1558PrintLog(PRINT_NORMAL, "[MOD] Saved mod settings for mod %s", currentMod->id.c_str());1559return;1560}15611562// i'm going to hell for this1563// nvm im actually so proud of this func yall have no idea i'm insane1564void SuperInternal(RSDK::ObjectClass *super, RSDK::ModSuper callback, void *data)1565{1566using namespace RSDK;15671568ModInfo *curMod = currentMod;1569bool32 override = false;1570if (!super->inherited)1571return; // Mod.Super on an object that's literally an original object why did you do this1572++superLevels[inheritLevel];1573if (HASH_MATCH_MD5(super->hash, super->inherited->hash)) {1574// entity override1575override = true;1576for (int32 i = 0; i < superLevels[inheritLevel]; i++) {1577if (!super->inherited)1578break; // *do not* cap superLevel because if we do we'll break things even more than what we had to do to get here1579super = super->inherited;1580}1581}1582else {1583// basic entity inherit1584inheritLevel++;1585super = super->inherited;1586}15871588switch (callback) {1589case SUPER_UPDATE:1590if (super->update)1591super->update();1592break;15931594case SUPER_LATEUPDATE:1595if (super->lateUpdate)1596super->lateUpdate();1597break;15981599case SUPER_STATICUPDATE:1600if (super->staticUpdate)1601super->staticUpdate();1602break;16031604case SUPER_DRAW:1605if (super->draw)1606super->draw();1607break;16081609case SUPER_CREATE:1610if (super->create)1611super->create(data);1612break;16131614case SUPER_STAGELOAD:1615if (super->stageLoad)1616super->stageLoad();1617break;16181619case SUPER_EDITORLOAD:1620if (super->editorLoad)1621super->editorLoad();1622break;16231624case SUPER_EDITORDRAW:1625if (super->editorDraw)1626super->editorDraw();1627break;16281629case SUPER_SERIALIZE:1630if (super->serialize)1631super->serialize();1632break;1633}16341635if (!override)1636inheritLevel--;1637superLevels[inheritLevel]--;1638currentMod = curMod;1639}16401641void RSDK::Super(int32 objectID, ModSuper callback, void *data) { return SuperInternal(&objectClassList[stageObjectIDs[objectID]], callback, data); }16421643void *RSDK::GetGlobals() { return (void *)globalVarsPtr; }16441645void RSDK::ModRegisterGlobalVariables(const char *globalsPath, void **globals, uint32 size)1646{1647AllocateStorage(globals, size, DATASET_STG, true);1648FileInfo info;1649InitFileInfo(&info);16501651int32 *varPtr = *(int32 **)globals;1652if (LoadFile(&info, globalsPath, FMODE_RB)) {1653uint8 varCount = ReadInt8(&info);1654for (int32 i = 0; i < varCount && globalVarsPtr; ++i) {1655int32 offset = ReadInt32(&info, false);1656int32 count = ReadInt32(&info, false);1657for (int32 v = 0; v < count; ++v) {1658varPtr[offset + v] = ReadInt32(&info, false);1659}1660}16611662CloseFile(&info);1663}1664}16651666#if RETRO_REV0U1667void RSDK::ModRegisterObject(Object **staticVars, Object **modStaticVars, const char *name, uint32 entityClassSize, uint32 staticClassSize,1668uint32 modClassSize, void (*update)(), void (*lateUpdate)(), void (*staticUpdate)(), void (*draw)(),1669void (*create)(void *), void (*stageLoad)(), void (*editorLoad)(), void (*editorDraw)(), void (*serialize)(),1670void (*staticLoad)(Object *), const char *inherited)1671{1672return ModRegisterObject_STD(staticVars, modStaticVars, name, entityClassSize, staticClassSize, modClassSize, update, lateUpdate, staticUpdate,1673draw, create, stageLoad, editorLoad, editorDraw, serialize, staticLoad, inherited);1674}16751676void RSDK::ModRegisterObject_STD(Object **staticVars, Object **modStaticVars, const char *name, uint32 entityClassSize, uint32 staticClassSize,1677uint32 modClassSize, std::function<void()> update, std::function<void()> lateUpdate,1678std::function<void()> staticUpdate, std::function<void()> draw, std::function<void(void *)> create,1679std::function<void()> stageLoad, std::function<void()> editorLoad, std::function<void()> editorDraw,1680std::function<void()> serialize, std::function<void(Object *)> staticLoad, const char *inherited)1681#else16821683void RSDK::ModRegisterObject(Object **staticVars, Object **modStaticVars, const char *name, uint32 entityClassSize, uint32 staticClassSize,1684uint32 modClassSize, void (*update)(), void (*lateUpdate)(), void (*staticUpdate)(), void (*draw)(),1685void (*create)(void *), void (*stageLoad)(), void (*editorLoad)(), void (*editorDraw)(), void (*serialize)(),1686const char *inherited)1687{1688return ModRegisterObject_STD(staticVars, modStaticVars, name, entityClassSize, staticClassSize, modClassSize, update, lateUpdate, staticUpdate,1689draw, create, stageLoad, editorLoad, editorDraw, serialize, inherited);1690}16911692void RSDK::ModRegisterObject_STD(Object **staticVars, Object **modStaticVars, const char *name, uint32 entityClassSize, uint32 staticClassSize,1693uint32 modClassSize, std::function<void()> update, std::function<void()> lateUpdate,1694std::function<void()> staticUpdate, std::function<void()> draw, std::function<void(void *)> create,1695std::function<void()> stageLoad, std::function<void()> editorLoad, std::function<void()> editorDraw,1696std::function<void()> serialize, const char *inherited)1697#endif1698{1699ModInfo *curMod = currentMod;1700int32 preCount = objectClassCount + 1;1701RETRO_HASH_MD5(hash);1702GEN_HASH_MD5(name, hash);17031704ObjectClass *inherit = NULL;1705for (int32 i = 0; i < objectClassCount; ++i) {1706if (HASH_MATCH_MD5(objectClassList[i].hash, hash)) {1707objectClassCount = i;1708inherit = new ObjectClass(objectClassList[i]);1709allocatedInherits.push_back(inherit);1710--preCount;1711if (!inherited)1712inherited = name;1713break;1714}1715}17161717if (inherited) {1718RETRO_HASH_MD5(hash);1719GEN_HASH_MD5(inherited, hash);1720if (!inherit) {1721for (int32 i = 0; i < preCount; ++i) {1722if (HASH_MATCH_MD5(objectClassList[i].hash, hash)) {1723inherit = new ObjectClass(objectClassList[i]);1724allocatedInherits.push_back(inherit);1725break;1726}1727}1728}17291730if (!inherit)1731inherited = NULL;1732}17331734if (inherited) {1735if (inherit->entityClassSize > entityClassSize)1736entityClassSize = inherit->entityClassSize;1737}17381739#if RETRO_REV0U1740RegisterObject_STD(staticVars, name, entityClassSize, staticClassSize, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,1741nullptr, nullptr);1742#else1743RegisterObject_STD(staticVars, name, entityClassSize, staticClassSize, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,1744nullptr);1745#endif17461747ObjectClass *info = &objectClassList[objectClassCount - 1];17481749// clang-format off1750if (update) info->update = [curMod, update]() { currentMod = curMod; update(); currentMod = NULL; };1751if (lateUpdate) info->lateUpdate = [curMod, lateUpdate]() { currentMod = curMod; lateUpdate(); currentMod = NULL; };1752if (staticUpdate) info->staticUpdate = [curMod, staticUpdate]() { currentMod = curMod; staticUpdate(); currentMod = NULL; };1753if (draw) info->draw = [curMod, draw]() { currentMod = curMod; draw(); currentMod = NULL; };1754if (create) info->create = [curMod, create](void* data) { currentMod = curMod; create(data); currentMod = NULL; };1755if (stageLoad) info->stageLoad = [curMod, stageLoad]() { currentMod = curMod; stageLoad(); currentMod = NULL; };1756#if RETRO_REV0U1757if (staticLoad) info->staticLoad = [curMod, staticLoad](Object *staticVars) { currentMod = curMod; staticLoad(staticVars); currentMod = NULL; };1758#endif1759if (editorLoad) info->editorLoad = [curMod, editorLoad]() { currentMod = curMod; editorLoad(); currentMod = NULL; };1760if (editorDraw) info->editorDraw = [curMod, editorDraw]() { currentMod = curMod; editorDraw(); currentMod = NULL; };1761if (serialize) info->serialize = [curMod, serialize]() { currentMod = curMod; serialize(); currentMod = NULL; };1762// clang-format on17631764if (inherited) {1765info->inherited = inherit;17661767if (HASH_MATCH_MD5(info->hash, inherit->hash)) {1768// we override an obj and lets set staticVars1769info->staticVars = inherit->staticVars;1770info->staticClassSize = inherit->staticClassSize;1771if (staticVars) {1772// give them a hook1773ModRegisterObjectHook(staticVars, name);1774}1775// lets also setup mod static vars1776if (modStaticVars && modClassSize) {1777curMod->staticVars[info->hash] = { curMod->id + "_" + name, modStaticVars, modClassSize };1778}1779}17801781// clang-format off1782if (!update) info->update = [curMod, info]() { currentMod = curMod; SuperInternal(info, SUPER_UPDATE, NULL); currentMod = NULL; };1783if (!lateUpdate) info->lateUpdate = [curMod, info]() { currentMod = curMod; SuperInternal(info, SUPER_LATEUPDATE, NULL); currentMod = NULL; };1784if (!staticUpdate) info->staticUpdate = [curMod, info]() { currentMod = curMod; SuperInternal(info, SUPER_STATICUPDATE, NULL); currentMod = NULL; };1785if (!draw) info->draw = [curMod, info]() { currentMod = curMod; SuperInternal(info, SUPER_DRAW, NULL); currentMod = NULL; };1786if (!create) info->create = [curMod, info](void* data) { currentMod = curMod; SuperInternal(info, SUPER_CREATE, data); currentMod = NULL; };1787if (!stageLoad) info->stageLoad = [curMod, info]() { currentMod = curMod; SuperInternal(info, SUPER_STAGELOAD, NULL); currentMod = NULL; };1788#if RETRO_REV0U1789// Don't inherit staticLoad, that should be per-struct1790// if (!staticLoad) info->staticLoad = [curMod, info](Object *staticVars) { currentMod = curMod; SuperInternal(info, SUPER_STATICLOAD, staticVars); currentMod = NULL; };1791#endif1792if (!editorLoad) info->editorLoad = [curMod, info]() { currentMod = curMod; SuperInternal(info, SUPER_EDITORLOAD, NULL); currentMod = NULL; };1793if (!editorDraw) info->editorDraw = [curMod, info]() { currentMod = curMod; SuperInternal(info, SUPER_EDITORDRAW, NULL); currentMod = NULL; };1794if (!serialize) info->serialize = [curMod, info]() { currentMod = curMod; SuperInternal(info, SUPER_SERIALIZE, NULL); currentMod = NULL; };1795// clang-format on1796}17971798objectClassCount = preCount;1799}18001801void RSDK::ModRegisterObjectHook(Object **staticVars, const char *staticName)1802{1803if (!staticVars || !staticName)1804return;18051806ObjectHook hook;1807GEN_HASH_MD5(staticName, hook.hash);1808hook.staticVars = staticVars;18091810objectHookList.push_back(hook);1811}18121813Object *RSDK::ModFindObject(const char *name)1814{1815if (int32 o = FindObject(name))1816return *objectClassList[stageObjectIDs[o]].staticVars;18171818return NULL;1819}18201821void RSDK::GetAchievementInfo(uint32 id, String *name, String *description, String *identifer, bool32 *achieved)1822{1823if (id >= achievementList.size())1824return;18251826if (name)1827InitString(name, achievementList[id].name.c_str(), 0);18281829if (description)1830InitString(description, achievementList[id].description.c_str(), 0);18311832if (identifer)1833InitString(identifer, achievementList[id].identifier.c_str(), 0);18341835if (achieved)1836*achieved = achievementList[id].achieved;1837}18381839int32 RSDK::GetAchievementIndexByID(const char *id)1840{1841for (int32 i = 0; i < achievementList.size(); ++i) {1842if (achievementList[i].identifier == std::string(id))1843return i;1844}18451846return -1;1847}1848int32 RSDK::GetAchievementCount() { return (int32)achievementList.size(); }18491850void RSDK::StateMachineRun(void (*state)(void))1851{1852bool32 skipState = false;18531854for (int32 h = 0; h < (int32)stateHookList.size(); ++h) {1855if (stateHookList[h].priority && stateHookList[h].state == state && stateHookList[h].hook)1856skipState |= stateHookList[h].hook(skipState);1857}18581859if (!skipState && state)1860state();18611862for (int32 h = 0; h < (int32)stateHookList.size(); ++h) {1863if (!stateHookList[h].priority && stateHookList[h].state == state && stateHookList[h].hook)1864stateHookList[h].hook(skipState);1865}1866}18671868bool32 RSDK::HandleRunState_HighPriority(void (*state)(void))1869{1870bool32 skipState = false;18711872for (int32 h = 0; h < (int32)stateHookList.size(); ++h) {1873if (stateHookList[h].priority && stateHookList[h].state == state && stateHookList[h].hook)1874skipState |= stateHookList[h].hook(skipState);1875}18761877return skipState;1878}18791880void RSDK::HandleRunState_LowPriority(void (*state)(void), bool32 skipState)1881{1882for (int32 h = 0; h < (int32)stateHookList.size(); ++h) {1883if (!stateHookList[h].priority && stateHookList[h].state == state && stateHookList[h].hook)1884stateHookList[h].hook(skipState);1885}1886}18871888void RSDK::RegisterStateHook(void (*state)(void), bool32 (*hook)(bool32 skippedState), bool32 priority)1889{1890if (!state)1891return;18921893StateHook stateHook;1894stateHook.state = state;1895stateHook.hook = hook;1896stateHook.priority = priority;18971898stateHookList.push_back(stateHook);1899}19001901#if RETRO_MOD_LOADER_VER >= 219021903// Files1904bool32 RSDK::ExcludeFile(const char *id, const char *path)1905{1906if (!id)1907return false;19081909if (!strlen(id) && currentMod)1910id = currentMod->id.c_str();19111912int32 m;1913for (m = 0; m < modList.size(); ++m) {1914if (modList[m].active && modList[m].id == id)1915break;1916}19171918if (m == modList.size())1919return false;19201921char pathLower[0x100];1922memset(pathLower, 0, sizeof(pathLower));1923for (int32 c = 0; c < strlen(path); ++c) pathLower[c] = tolower(path[c]);19241925auto &excludeList = modList[m].excludedFiles;1926if (std::find(excludeList.begin(), excludeList.end(), pathLower) == excludeList.end()) {1927excludeList.push_back(std::string(pathLower));19281929return true;1930}19311932return false;1933}1934bool32 RSDK::ExcludeAllFiles(const char *id)1935{1936if (!id)1937return false;19381939if (!strlen(id) && currentMod)1940id = currentMod->id.c_str();19411942int32 m;1943for (m = 0; m < modList.size(); ++m) {1944if (modList[m].active && modList[m].id == id)1945break;1946}19471948if (m == modList.size())1949return false;19501951auto &excludeList = modList[m].excludedFiles;1952for (auto file : modList[m].fileMap) {1953excludeList.push_back(file.first);1954}19551956modList[m].fileMap.clear();19571958return true;1959}1960bool32 RSDK::ReloadFile(const char *id, const char *path)1961{1962if (!id)1963return false;19641965if (!strlen(id) && currentMod)1966id = currentMod->id.c_str();19671968int32 m;1969for (m = 0; m < modList.size(); ++m) {1970if (modList[m].active && modList[m].id == id)1971break;1972}19731974if (m == modList.size())1975return false;19761977char pathLower[0x100];1978memset(pathLower, 0, sizeof(pathLower));1979for (int32 c = 0; c < strlen(path); ++c) pathLower[c] = tolower(path[c]);19801981auto &excludeList = modList[m].excludedFiles;1982if (std::find(excludeList.begin(), excludeList.end(), pathLower) != excludeList.end()) {1983excludeList.erase(std::remove(excludeList.begin(), excludeList.end(), pathLower), excludeList.end());19841985return true;1986}19871988ScanModFolder(&modList[m], path);19891990return true;1991}1992bool32 RSDK::ReloadAllFiles(const char *id)1993{1994if (!id)1995return false;19961997if (!strlen(id) && currentMod)1998id = currentMod->id.c_str();19992000int32 m;2001for (m = 0; m < modList.size(); ++m) {2002if (modList[m].active && modList[m].id == id)2003break;2004}20052006if (m == modList.size())2007return false;20082009modList[m].excludedFiles.clear();2010ScanModFolder(&modList[m]);20112012return true;2013}20142015// Objects & Entities2016bool32 RSDK::GetGroupEntities(uint16 group, void **entity)2017{2018if (group >= TYPEGROUP_COUNT)2019return false;20202021if (!entity)2022return false;20232024if (*entity) {2025++foreachStackPtr->id;2026}2027else {2028foreachStackPtr++;2029foreachStackPtr->id = 0;2030}20312032for (Entity *nextEntity = &objectEntityList[typeGroups[group].entries[foreachStackPtr->id]]; foreachStackPtr->id < typeGroups[group].entryCount;2033++foreachStackPtr->id, nextEntity = &objectEntityList[typeGroups[group].entries[foreachStackPtr->id]]) {2034if (nextEntity->group == group) {2035*entity = nextEntity;2036return true;2037}2038}20392040foreachStackPtr--;20412042return false;2043}20442045#endif20462047#endif204820492050