Path: blob/main/RSDKv4/ModAPI.cpp
817 views
#include "RetroEngine.hpp"12#if RETRO_USE_MOD_LOADER || !RETRO_USE_ORIGINAL_CODE3char savePath[0x100];4#endif56char playerNames[PLAYER_COUNT][0x20];7byte playerCount = 0;89#if RETRO_USE_MOD_LOADER10std::vector<ModInfo> modList;11int activeMod = -1;1213char modsPath[0x100];1415bool redirectSave = false;1617char modTypeNames[OBJECT_COUNT][0x40];18char modScriptPaths[OBJECT_COUNT][0x40];19byte modScriptFlags[OBJECT_COUNT];20byte modObjCount = 0;2122#include <filesystem>23#include <locale>2425void OpenModMenu()26{27Engine.gameMode = ENGINE_INITMODMENU;28Engine.modMenuCalled = true;29}3031#if RETRO_PLATFORM == RETRO_ANDROID32namespace fs = std::__fs::filesystem; // this is so we can avoid using c++17, which causes a ton of warnings w asio and looks ugly33#else34namespace fs = std::filesystem;35#endif3637fs::path resolvePath(fs::path given)38{39if (given.is_relative())40given = fs::current_path() / given; // thanks for the weird syntax!41for (auto &p : fs::directory_iterator{ given.parent_path() }) {42char pbuf[0x100];43char gbuf[0x100];44auto pf = p.path().filename();45auto pstr = pf.string();46StringLowerCase(pbuf, pstr.c_str());47auto gf = given.filename();48auto gstr = gf.string();49StringLowerCase(gbuf, gstr.c_str());50if (StrComp(pbuf, gbuf)) {51return p.path();52}53}54return given; // might work might not!55}5657void InitMods()58{59modList.clear();60forceUseScripts = forceUseScripts_Config;61skipStartMenu = skipStartMenu_Config;62disableFocusPause = disableFocusPause_Config;63redirectSave = false;64Engine.forceSonic1 = false;65sprintf(savePath, "");6667char modBuf[0x100];68sprintf(modBuf, "%smods", modsPath);6970fs::path modPath = resolvePath(modBuf);7172if (fs::exists(modPath) && fs::is_directory(modPath)) {73std::string mod_config = modPath.string() + "/modconfig.ini";74FileIO *configFile = fOpen(mod_config.c_str(), "r");75if (configFile) {76fClose(configFile);77IniParser modConfig(mod_config.c_str(), false);7879for (int m = 0; m < modConfig.items.size(); ++m) {80bool active = false;81ModInfo info;82modConfig.GetBool("mods", modConfig.items[m].key, &active);83if (LoadMod(&info, modPath.string(), modConfig.items[m].key, active))84modList.push_back(info);85}86}8788try {89auto rdi = fs::directory_iterator(modPath);90for (auto de : rdi) {91if (de.is_directory()) {92fs::path modDirPath = de.path();9394ModInfo info;9596std::string modDir = modDirPath.string().c_str();97const std::string mod_inifile = modDir + "/mod.ini";98std::string folder = modDirPath.filename().string();99100bool flag = true;101for (int m = 0; m < modList.size(); ++m) {102if (modList[m].folder == folder) {103flag = false;104break;105}106}107108if (flag) {109if (LoadMod(&info, modPath.string(), modDirPath.filename().string(), false))110modList.push_back(info);111}112}113}114} catch (fs::filesystem_error fe) {115PrintLog("Mods Folder Scanning Error: ");116PrintLog(fe.what());117}118}119120forceUseScripts = forceUseScripts_Config;121skipStartMenu = skipStartMenu_Config;122disableFocusPause = disableFocusPause_Config;123redirectSave = false;124Engine.forceSonic1 = false;125sprintf(savePath, "");126for (int m = 0; m < modList.size(); ++m) {127if (!modList[m].active)128continue;129if (modList[m].useScripts)130forceUseScripts = true;131if (modList[m].skipStartMenu)132skipStartMenu = true;133if (modList[m].disableFocusPause)134disableFocusPause |= modList[m].disableFocusPause;135if (modList[m].redirectSave) {136sprintf(savePath, "%s", modList[m].savePath.c_str());137redirectSave = true;138}139if (modList[m].forceSonic1)140Engine.forceSonic1 = true;141}142143ReadSaveRAMData();144ReadUserdata();145}146bool LoadMod(ModInfo *info, std::string modsPath, std::string folder, bool active)147{148if (!info)149return false;150151info->fileMap.clear();152info->name = "";153info->desc = "";154info->author = "";155info->version = "";156info->folder = "";157info->active = false;158159const std::string modDir = modsPath + "/" + folder;160161FileIO *f = fOpen((modDir + "/mod.ini").c_str(), "r");162if (f) {163fClose(f);164IniParser modSettings((modDir + "/mod.ini").c_str(), false);165166info->name = "Unnamed Mod";167info->desc = "";168info->author = "Unknown Author";169info->version = "1.0.0";170info->folder = folder;171172char infoBuf[0x100];173// Name174StrCopy(infoBuf, "");175modSettings.GetString("", "Name", infoBuf);176if (!StrComp(infoBuf, ""))177info->name = infoBuf;178// Desc179StrCopy(infoBuf, "");180modSettings.GetString("", "Description", infoBuf);181if (!StrComp(infoBuf, ""))182info->desc = infoBuf;183// Author184StrCopy(infoBuf, "");185modSettings.GetString("", "Author", infoBuf);186if (!StrComp(infoBuf, ""))187info->author = infoBuf;188// Version189StrCopy(infoBuf, "");190modSettings.GetString("", "Version", infoBuf);191if (!StrComp(infoBuf, ""))192info->version = infoBuf;193194info->active = active;195196ScanModFolder(info);197198info->useScripts = false;199modSettings.GetBool("", "TxtScripts", &info->useScripts);200if (info->useScripts && info->active)201forceUseScripts = true;202203info->skipStartMenu = false;204modSettings.GetBool("", "SkipStartMenu", &info->skipStartMenu);205if (info->skipStartMenu && info->active)206skipStartMenu = true;207208info->disableFocusPause = false;209modSettings.GetInteger("", "DisableFocusPause", &info->disableFocusPause);210if (info->disableFocusPause && info->active)211disableFocusPause |= info->disableFocusPause;212213info->redirectSave = false;214modSettings.GetBool("", "RedirectSaveRAM", &info->redirectSave);215if (info->redirectSave) {216char path[0x100];217sprintf(path, "mods/%s/", folder.c_str());218info->savePath = path;219}220221info->forceSonic1 = false;222modSettings.GetBool("", "ForceSonic1", &info->forceSonic1);223if (info->forceSonic1 && info->active)224Engine.forceSonic1 = true;225226return true;227}228return false;229}230231void ScanModFolder(ModInfo *info)232{233if (!info)234return;235236char modBuf[0x100];237sprintf(modBuf, "%smods", modsPath);238239fs::path modPath = resolvePath(modBuf);240241const std::string modDir = modPath.string() + "/" + info->folder;242243info->fileMap.clear();244245// Check for Data/ replacements246fs::path dataPath = resolvePath(modDir + "/Data");247248if (fs::exists(dataPath) && fs::is_directory(dataPath)) {249try {250auto data_rdi = fs::recursive_directory_iterator(dataPath);251for (auto &data_de : data_rdi) {252if (data_de.is_regular_file()) {253char modBuf[0x100];254StrCopy(modBuf, data_de.path().string().c_str());255char folderTest[4][0x10] = {256"Data/",257"Data\\",258"data/",259"data\\",260};261int tokenPos = -1;262for (int i = 0; i < 4; ++i) {263tokenPos = FindLastStringToken(modBuf, folderTest[i]);264if (tokenPos >= 0)265break;266}267268if (tokenPos >= 0) {269char buffer[0x80];270for (int i = StrLength(modBuf); i >= tokenPos; --i) {271buffer[i - tokenPos] = modBuf[i] == '\\' ? '/' : modBuf[i];272}273274// PrintLog(modBuf);275std::string path(buffer);276std::string modPath(modBuf);277char pathLower[0x100];278memset(pathLower, 0, sizeof(char) * 0x100);279for (int c = 0; c < path.size(); ++c) {280pathLower[c] = tolower(path.c_str()[c]);281}282283info->fileMap.insert(std::pair<std::string, std::string>(pathLower, modBuf));284}285}286}287} catch (fs::filesystem_error fe) {288PrintLog("Data Folder Scanning Error: ");289PrintLog(fe.what());290}291}292293// Check for Bytecode/ replacements294fs::path bytecodePath = resolvePath(modDir + "/Bytecode");295296if (fs::exists(bytecodePath) && fs::is_directory(bytecodePath)) {297try {298auto data_rdi = fs::recursive_directory_iterator(bytecodePath);299for (auto &data_de : data_rdi) {300if (data_de.is_regular_file()) {301char modBuf[0x100];302StrCopy(modBuf, data_de.path().string().c_str());303char folderTest[4][0x10] = {304"Bytecode/",305"Bytecode\\",306"bytecode/",307"bytecode\\",308};309int tokenPos = -1;310for (int i = 0; i < 4; ++i) {311tokenPos = FindLastStringToken(modBuf, folderTest[i]);312if (tokenPos >= 0)313break;314}315316if (tokenPos >= 0) {317char buffer[0x80];318for (int i = StrLength(modBuf); i >= tokenPos; --i) {319buffer[i - tokenPos] = modBuf[i] == '\\' ? '/' : modBuf[i];320}321322// PrintLog(modBuf);323std::string path(buffer);324std::string modPath(modBuf);325char pathLower[0x100];326memset(pathLower, 0, sizeof(char) * 0x100);327for (int c = 0; c < path.size(); ++c) {328pathLower[c] = tolower(path.c_str()[c]);329}330331info->fileMap.insert(std::pair<std::string, std::string>(pathLower, modBuf));332}333}334}335} catch (fs::filesystem_error fe) {336PrintLog("Bytecode Folder Scanning Error: ");337PrintLog(fe.what());338}339}340}341342void SaveMods()343{344char modBuf[0x100];345sprintf(modBuf, "%smods", modsPath);346fs::path modPath = resolvePath(modBuf);347348if (fs::exists(modPath) && fs::is_directory(modPath)) {349std::string mod_config = modPath.string() + "/modconfig.ini";350IniParser modConfig;351352for (int m = 0; m < modList.size(); ++m) {353ModInfo *info = &modList[m];354355modConfig.SetBool("mods", info->folder.c_str(), info->active);356}357358modConfig.Write(mod_config.c_str(), false);359}360}361362void RefreshEngine()363{364// Reload entire engine365Engine.LoadGameConfig("Data/Game/GameConfig.bin");366#if RETRO_USING_SDL2367if (Engine.window) {368char gameTitle[0x40];369sprintf(gameTitle, "%s%s", Engine.gameWindowText, Engine.usingDataFile_Config ? "" : " (Using Data Folder)");370SDL_SetWindowTitle(Engine.window, gameTitle);371}372#elif RETRO_USING_SDL1373char gameTitle[0x40];374sprintf(gameTitle, "%s%s", Engine.gameWindowText, Engine.usingDataFile_Config ? "" : " (Using Data Folder)");375SDL_WM_SetCaption(gameTitle, NULL);376#endif377378ClearMeshData();379ClearTextures(true);380381nativeEntityCountBackup = 0;382memset(backupEntityList, 0, sizeof(backupEntityList));383memset(objectEntityBackup, 0, sizeof(objectEntityBackup));384385nativeEntityCountBackupS = 0;386memset(backupEntityListS, 0, sizeof(backupEntityListS));387memset(objectEntityBackupS, 0, sizeof(objectEntityBackupS));388389for (int i = 0; i < FONTLIST_COUNT; ++i) {390fontList[i].count = 2;391}392393ReleaseStageSfx();394ReleaseGlobalSfx();395LoadGlobalSfx();396InitLocalizedStrings();397398for (nativeEntityPos = 0; nativeEntityPos < nativeEntityCount; ++nativeEntityPos) {399NativeEntity *entity = &objectEntityBank[activeEntityList[nativeEntityPos]];400entity->eventCreate(entity);401}402403forceUseScripts = forceUseScripts_Config;404skipStartMenu = skipStartMenu_Config;405disableFocusPause = disableFocusPause_Config;406redirectSave = false;407Engine.forceSonic1 = false;408sprintf(savePath, "");409for (int m = 0; m < modList.size(); ++m) {410if (!modList[m].active)411continue;412if (modList[m].useScripts)413forceUseScripts = true;414if (modList[m].skipStartMenu)415skipStartMenu = true;416if (modList[m].disableFocusPause)417disableFocusPause |= modList[m].disableFocusPause;418if (modList[m].redirectSave) {419sprintf(savePath, "%s", modList[m].savePath.c_str());420redirectSave = true;421}422if (modList[m].forceSonic1)423Engine.forceSonic1 = true;424}425426Engine.gameType = GAME_SONIC2;427if (strstr(Engine.gameWindowText, "Sonic 1") || Engine.forceSonic1) {428Engine.gameType = GAME_SONIC1;429}430431achievementCount = 0;432if (Engine.gameType == GAME_SONIC1) {433AddAchievement("Ramp Ring Acrobatics",434"Without touching the ground,\rcollect all the rings in a\rtrapezoid formation in Green\rHill Zone Act 1");435AddAchievement("Blast Processing", "Clear Green Hill Zone Act 1\rin under 30 seconds");436AddAchievement("Secret of Marble Zone", "Travel though a secret\rroom in Marbale Zone Act 3");437AddAchievement("Block Buster", "Break 16 blocks in a row\rwithout stopping");438AddAchievement("Ring King", "Collect 200 Rings");439AddAchievement("Secret of Labyrinth Zone", "Activate and ride the\rhidden platform in\rLabyrinth Zone Act 1");440AddAchievement("Flawless Pursuit", "Clear the boss in Labyrinth\rZone without getting hurt");441AddAchievement("Bombs Away", "Defeat the boss in Starlight Zone\rusing only the see-saw bombs");442AddAchievement("Hidden Transporter", "Collect 50 Rings and take the hidden transporter path\rin Scrap Brain Act 2");443AddAchievement("Chaos Connoisseur", "Collect all the chaos\remeralds");444AddAchievement("One For the Road", "As a parting gift, land a\rfinal hit on Dr. Eggman's\rescaping Egg Mobile");445AddAchievement("Beat The Clock", "Clear the Time Attack\rmode in less than 45\rminutes");446}447else if (Engine.gameType == GAME_SONIC2) {448AddAchievement("Quick Run", "Complete Emerald Hill\rZone Act 1 in under 35\rseconds");449AddAchievement("100% Chemical Free", "Complete Chemical Plant\rwithout going underwater");450AddAchievement("Early Bird Special", "Collect all the Chaos\rEmeralds before Chemical\rPlant");451AddAchievement("Superstar", "Complete any Act as\rSuper Sonic");452AddAchievement("Hit it Big", "Get a jackpot on the Casino Night slot machines");453AddAchievement("Bop Non-stop", "Defeat any boss in 8\rconsecutive hits without\rtouching he ground");454AddAchievement("Perfectionist", "Get a Perfect Bonus by\rcollecting every Ring in an\rAct");455AddAchievement("A Secret Revealed", "Find and complete\rHidden Palace Zone");456AddAchievement("Head 2 Head", "Win a 2P Versus race\ragainst a friend");457AddAchievement("Metropolis Master", "Complete Any Metropolis\rZone Act without getting\rhurt");458AddAchievement("Scrambled Egg", "Defeat Dr. Eggman's Boss\rAttack mode in under 7\rminutes");459AddAchievement("Beat the Clock", "Complete the Time Attack\rmode in less than 45\rminutes");460}461462SaveMods();463464ReadSaveRAMData();465ReadUserdata();466}467468void GetModCount() { scriptEng.checkResult = (int)modList.size(); }469void GetModName(int *textMenu, int *highlight, uint *id, int *unused)470{471if (*id >= modList.size())472return;473474TextMenu *menu = &gameMenu[*textMenu];475menu->entryHighlight[menu->rowCount] = *highlight;476AddTextMenuEntry(menu, modList[*id].name.c_str());477}478479void GetModDescription(int *textMenu, int *highlight, uint *id, int *unused)480{481if (*id >= modList.size())482return;483484TextMenu *menu = &gameMenu[*textMenu];485menu->entryHighlight[menu->rowCount] = *highlight;486AddTextMenuEntry(menu, modList[*id].desc.c_str());487}488489void GetModAuthor(int *textMenu, int *highlight, uint *id, int *unused)490{491if (*id >= modList.size())492return;493494TextMenu *menu = &gameMenu[*textMenu];495menu->entryHighlight[menu->rowCount] = *highlight;496AddTextMenuEntry(menu, modList[*id].author.c_str());497}498499void GetModVersion(int *textMenu, int *highlight, uint *id, int *unused)500{501if (*id >= modList.size())502return;503504TextMenu *menu = &gameMenu[*textMenu];505menu->entryHighlight[menu->rowCount] = *highlight;506AddTextMenuEntry(menu, modList[*id].version.c_str());507}508509void GetModActive(uint *id, int *unused)510{511scriptEng.checkResult = false;512if (*id >= modList.size())513return;514515scriptEng.checkResult = modList[*id].active;516}517518void SetModActive(uint *id, int *active)519{520if (*id >= modList.size())521return;522523modList[*id].active = *active;524}525526void MoveMod(uint *id, int *up)527{528if (!id || !up)529return;530531int preOption = *id;532int option = preOption + (*up ? -1 : 1);533if (option < 0 || preOption < 0)534return;535536if (option >= (int)modList.size() || preOption >= (int)modList.size())537return;538539ModInfo swap = modList[preOption];540modList[preOption] = modList[option];541modList[option] = swap;542}543544#endif545546#if RETRO_USE_MOD_LOADER || !RETRO_USE_ORIGINAL_CODE547int GetSceneID(byte listID, const char *sceneName)548{549if (listID >= 3)550return -1;551552char scnName[0x40];553int scnPos = 0;554int pos = 0;555while (sceneName[scnPos]) {556if (sceneName[scnPos] != ' ')557scnName[pos++] = sceneName[scnPos];558++scnPos;559}560scnName[pos] = 0;561562for (int s = 0; s < stageListCount[listID]; ++s) {563char nameBuffer[0x40];564565scnPos = 0;566pos = 0;567while (stageList[listID][s].name[scnPos]) {568if (stageList[listID][s].name[scnPos] != ' ')569nameBuffer[pos++] = stageList[listID][s].name[scnPos];570++scnPos;571}572nameBuffer[pos] = 0;573574if (StrComp(scnName, nameBuffer)) {575return s;576}577}578return -1;579}580#endif581582583