Path: blob/main/RSDKv4/RetroEngine.cpp
817 views
#include "RetroEngine.hpp"12#if !RETRO_USE_ORIGINAL_CODE3bool usingCWD = false;4bool engineDebugMode = false;5#endif67#if RETRO_PLATFORM == RETRO_ANDROID8#include <unistd.h>9#endif1011RetroEngine Engine = RetroEngine();1213#if !RETRO_USE_ORIGINAL_CODE14inline int GetLowerRate(int intendRate, int targetRate)15{16int result = 0;17int valStore = 0;1819result = targetRate;20if (intendRate) {21do {22valStore = result % intendRate;23result = intendRate;24intendRate = valStore;25} while (valStore);26}27return result;28}29#endif3031bool ProcessEvents()32{33#if !RETRO_USE_ORIGINAL_CODE34#if RETRO_USING_SDL1 || RETRO_USING_SDL235while (SDL_PollEvent(&Engine.sdlEvents)) {36// Main Events37switch (Engine.sdlEvents.type) {38#if RETRO_USING_SDL239case SDL_WINDOWEVENT:40switch (Engine.sdlEvents.window.event) {41case SDL_WINDOWEVENT_MAXIMIZED: {42SDL_RestoreWindow(Engine.window);43SDL_SetWindowFullscreen(Engine.window, SDL_WINDOW_FULLSCREEN_DESKTOP);44SDL_ShowCursor(SDL_FALSE);45Engine.isFullScreen = true;46break;47}48case SDL_WINDOWEVENT_CLOSE: return false;49case SDL_WINDOWEVENT_FOCUS_LOST:50if (Engine.gameMode == ENGINE_MAINGAME && !(disableFocusPause & 1))51Engine.gameMode = ENGINE_INITPAUSE;52#if RETRO_REV0053if (!(disableFocusPause & 1))54Engine.message = MESSAGE_LOSTFOCUS;55#endif56Engine.hasFocus = false;57break;58case SDL_WINDOWEVENT_FOCUS_GAINED: Engine.hasFocus = true; break;59}60break;61case SDL_CONTROLLERDEVICEADDED: controllerInit(Engine.sdlEvents.cdevice.which); break;62case SDL_CONTROLLERDEVICEREMOVED: controllerClose(Engine.sdlEvents.cdevice.which); break;63case SDL_APP_WILLENTERBACKGROUND:64if (Engine.gameMode == ENGINE_MAINGAME && !(disableFocusPause & 1))65Engine.gameMode = ENGINE_INITPAUSE;66#if RETRO_REV0067if (!(disableFocusPause & 1))68Engine.message = MESSAGE_LOSTFOCUS;69#endif70Engine.hasFocus = false;71break;72case SDL_APP_WILLENTERFOREGROUND: Engine.hasFocus = true; break;73case SDL_APP_TERMINATING: return false;74#endif7576#if RETRO_USING_SDL2 && defined(RETRO_USING_MOUSE)77case SDL_MOUSEMOTION:78if (touches <= 1) { // Touch always takes priority over mouse79uint state = SDL_GetMouseState(&touchX[0], &touchY[0]);8081touchXF[0] = (((touchX[0] - displaySettings.offsetX) / (float)displaySettings.width) * SCREEN_XSIZE_F) - SCREEN_CENTERX_F;82touchYF[0] = -(((touchY[0] / (float)displaySettings.height) * SCREEN_YSIZE_F) - SCREEN_CENTERY_F);83touchX[0] = ((touchX[0] - displaySettings.offsetX) / (float)displaySettings.width) * SCREEN_XSIZE;84touchY[0] = (touchY[0] / (float)displaySettings.height) * SCREEN_YSIZE;8586touchDown[0] = state & SDL_BUTTON_LMASK;87if (touchDown[0])88touches = 1;89}90break;91case SDL_MOUSEBUTTONDOWN:92if (touches <= 1) { // Touch always takes priority over mouse93switch (Engine.sdlEvents.button.button) {94case SDL_BUTTON_LEFT: touchDown[0] = true; break;95}96touches = 1;97}98break;99case SDL_MOUSEBUTTONUP:100if (touches <= 1) { // Touch always takes priority over mouse101switch (Engine.sdlEvents.button.button) {102case SDL_BUTTON_LEFT: touchDown[0] = false; break;103}104touches = 0;105}106break;107#endif108109#if defined(RETRO_USING_TOUCH) && RETRO_USING_SDL2110case SDL_FINGERMOTION:111case SDL_FINGERDOWN:112case SDL_FINGERUP: {113int count = SDL_GetNumTouchFingers(Engine.sdlEvents.tfinger.touchId);114touches = 0;115for (int i = 0; i < count; i++) {116SDL_Finger *finger = SDL_GetTouchFinger(Engine.sdlEvents.tfinger.touchId, i);117if (finger) {118touchDown[touches] = true;119touchX[touches] = finger->x * SCREEN_XSIZE;120touchY[touches] = finger->y * SCREEN_YSIZE;121touchXF[touches] = (finger->x * SCREEN_XSIZE_F) - SCREEN_CENTERX_F;122touchYF[touches] = -((finger->y * SCREEN_YSIZE_F) - SCREEN_CENTERY_F);123124touchX[touches] -= displaySettings.offsetX;125touchXF[touches] -= displaySettings.offsetX;126127touches++;128}129}130break;131}132#endif //! RETRO_USING_SDL2133134case SDL_KEYDOWN:135switch (Engine.sdlEvents.key.keysym.sym) {136default: break;137case SDLK_ESCAPE:138if (Engine.devMenu) {139#if RETRO_USE_MOD_LOADER140// hacky patch because people can escape141if (Engine.gameMode == ENGINE_DEVMENU && stageMode == DEVMENU_MODMENU)142RefreshEngine();143#endif144ClearNativeObjects();145CREATE_ENTITY(RetroGameLoop);146if (Engine.gameDeviceType == RETRO_MOBILE)147CREATE_ENTITY(VirtualDPad);148Engine.gameMode = ENGINE_INITDEVMENU;149}150break;151152case SDLK_F1:153if (Engine.devMenu) {154activeStageList = 0;155stageListPosition = 0;156stageMode = STAGEMODE_LOAD;157Engine.gameMode = ENGINE_MAINGAME;158}159break;160161case SDLK_F2:162if (Engine.devMenu) {163stageListPosition--;164if (stageListPosition < 0) {165activeStageList--;166167if (activeStageList < 0) {168activeStageList = 3;169}170stageListPosition = stageListCount[activeStageList] - 1;171}172stageMode = STAGEMODE_LOAD;173Engine.gameMode = ENGINE_MAINGAME;174SetGlobalVariableByName("lampPostID", 0); // For S1175SetGlobalVariableByName("starPostID", 0); // For S2176}177break;178179case SDLK_F3:180if (Engine.devMenu) {181stageListPosition++;182if (stageListPosition >= stageListCount[activeStageList]) {183activeStageList++;184185stageListPosition = 0;186187if (activeStageList >= 4) {188activeStageList = 0;189}190}191stageMode = STAGEMODE_LOAD;192Engine.gameMode = ENGINE_MAINGAME;193SetGlobalVariableByName("lampPostID", 0); // For S1194SetGlobalVariableByName("starPostID", 0); // For S2195}196break;197198case SDLK_F4:199Engine.isFullScreen ^= 1;200SetFullScreen(Engine.isFullScreen);201break;202203case SDLK_F5:204if (Engine.devMenu) {205currentStageFolder[0] = 0; // reload all assets & scripts206stageMode = STAGEMODE_LOAD;207}208break;209210case SDLK_F8:211if (Engine.devMenu)212showHitboxes ^= 2;213break;214215case SDLK_F9:216if (Engine.devMenu)217showHitboxes ^= 1;218break;219220case SDLK_F10:221if (Engine.devMenu)222Engine.showPaletteOverlay ^= 1;223break;224225case SDLK_BACKSPACE:226if (Engine.devMenu)227Engine.gameSpeed = Engine.fastForwardSpeed;228break;229230#if RETRO_PLATFORM == RETRO_OSX231case SDLK_F6:232if (Engine.masterPaused)233Engine.frameStep = true;234break;235236case SDLK_F7:237if (Engine.devMenu)238Engine.masterPaused ^= 1;239break;240#else241case SDLK_F11:242case SDLK_INSERT:243if (Engine.masterPaused)244Engine.frameStep = true;245break;246247case SDLK_F12:248case SDLK_PAUSE:249if (Engine.devMenu)250Engine.masterPaused ^= 1;251break;252#endif253}254255#if RETRO_USING_SDL1256keyState[Engine.sdlEvents.key.keysym.sym] = 1;257#endif258break;259case SDL_KEYUP:260switch (Engine.sdlEvents.key.keysym.sym) {261default: break;262case SDLK_BACKSPACE: Engine.gameSpeed = 1; break;263}264#if RETRO_USING_SDL1265keyState[Engine.sdlEvents.key.keysym.sym] = 0;266#endif267break;268case SDL_QUIT: return false;269}270}271#endif272#endif273return true;274}275276void RetroEngine::Init()277{278CalculateTrigAngles();279GenerateBlendLookupTable();280281CloseRSDKContainers(); // Clears files282283Engine.usingDataFile = false;284Engine.usingBytecode = false;285286#if !RETRO_USE_ORIGINAL_CODE287InitUserdata();288#if RETRO_USE_MOD_LOADER289InitMods();290#endif291#if RETRO_USE_NETWORKING292InitNetwork();293#endif294295char dest[0x200];296#if RETRO_PLATFORM == RETRO_UWP297static char resourcePath[256] = { 0 };298299if (strlen(resourcePath) == 0) {300auto folder = winrt::Windows::Storage::ApplicationData::Current().LocalFolder();301auto path = to_string(folder.Path());302303std::copy(path.begin(), path.end(), resourcePath);304}305306strcpy(dest, resourcePath);307strcat(dest, "\\");308strcat(dest, Engine.dataFile);309#elif RETRO_PLATFORM == RETRO_ANDROID310StrCopy(dest, gamePath);311StrAdd(dest, Engine.dataFile[0]);312disableFocusPause = 0; // focus pause is ALWAYS enabled.313#else314315StrCopy(dest, BASE_PATH);316StrAdd(dest, Engine.dataFile[0]);317#endif318CheckRSDKFile(dest);319#else320CheckRSDKFile("Data.rsdk");321#endif322323#if !RETRO_USE_ORIGINAL_CODE324for (int i = 1; i < RETRO_PACK_COUNT; ++i) {325if (!StrComp(Engine.dataFile[i], "")) {326StrCopy(dest, BASE_PATH);327StrAdd(dest, Engine.dataFile[i]);328CheckRSDKFile(dest);329}330}331#endif332333gameMode = ENGINE_MAINGAME;334running = false;335#if !RETRO_USE_ORIGINAL_CODE336bool skipStart = skipStartMenu;337#endif338SaveGame *saveGame = (SaveGame *)saveRAM;339340if (LoadGameConfig("Data/Game/GameConfig.bin")) {341if (InitRenderDevice()) {342if (InitAudioPlayback()) {343InitFirstStage();344ClearScriptData();345initialised = true;346running = true;347348#if !RETRO_USE_ORIGINAL_CODE349if ((startList_Game != 0xFF && startList_Game) || (startStage_Game != 0xFF && startStage_Game) || startPlayer != 0xFF) {350skipStart = true;351InitStartingStage(startList_Game == 0xFF ? STAGELIST_PRESENTATION : startList_Game, startStage_Game == 0xFF ? 0 : startStage_Game,352startPlayer == 0xFF ? 0 : startPlayer);353}354else if (startSave != 0xFF && startSave <= 4) {355if (startSave == 0) {356SetGlobalVariableByName("options.saveSlot", 0);357SetGlobalVariableByName("options.gameMode", 0);358359SetGlobalVariableByName("options.stageSelectFlag", 0);360SetGlobalVariableByName("player.lives", 3);361SetGlobalVariableByName("player.score", 0);362SetGlobalVariableByName("player.scoreBonus", 50000);363SetGlobalVariableByName("specialStage.emeralds", 0);364SetGlobalVariableByName("specialStage.listPos", 0);365SetGlobalVariableByName("stage.player2Enabled", 0);366SetGlobalVariableByName("lampPostID", 0); // For S1367SetGlobalVariableByName("starPostID", 0); // For S2368SetGlobalVariableByName("options.vsMode", 0);369370SetGlobalVariableByName("specialStage.nextZone", 0);371InitStartingStage(STAGELIST_REGULAR, 0, 0);372}373else {374SetGlobalVariableByName("options.saveSlot", startSave);375SetGlobalVariableByName("options.gameMode", 1);376int slot = (startSave - 1) << 3;377378SetGlobalVariableByName("options.stageSelectFlag", false);379SetGlobalVariableByName("player.lives", saveGame->files[slot].lives);380SetGlobalVariableByName("player.score", saveGame->files[slot].score);381SetGlobalVariableByName("player.scoreBonus", saveGame->files[slot].scoreBonus);382SetGlobalVariableByName("specialStage.emeralds", saveGame->files[slot].emeralds);383SetGlobalVariableByName("specialStage.listPos", saveGame->files[slot].specialStageID);384SetGlobalVariableByName("stage.player2Enabled", saveGame->files[slot].characterID == 3);385SetGlobalVariableByName("lampPostID", 0); // For S1386SetGlobalVariableByName("starPostID", 0); // For S2387SetGlobalVariableByName("options.vsMode", 0);388389int nextStage = saveGame->files[slot].stageID;390if (nextStage >= 0x80) {391SetGlobalVariableByName("specialStage.nextZone", nextStage - 0x81);392InitStartingStage(STAGELIST_SPECIAL, saveGame->files[slot].specialStageID, saveGame->files[slot].characterID);393}394else if (nextStage >= 1) {395SetGlobalVariableByName("specialStage.nextZone", nextStage - 1);396InitStartingStage(STAGELIST_REGULAR, nextStage - 1, saveGame->files[slot].characterID);397}398else {399saveGame->files[slot].characterID = 0;400saveGame->files[slot].lives = 3;401saveGame->files[slot].score = 0;402saveGame->files[slot].scoreBonus = 50000;403saveGame->files[slot].stageID = 0;404saveGame->files[slot].emeralds = 0;405saveGame->files[slot].specialStageID = 0;406saveGame->files[slot].unused = 0;407408SetGlobalVariableByName("specialStage.nextZone", 0);409InitStartingStage(STAGELIST_REGULAR, 0, 0);410}411}412skipStart = true;413}414#endif415}416}417}418419#if !RETRO_USE_ORIGINAL_CODE420gameType = GAME_SONIC2;421#if RETRO_USE_MOD_LOADER422if (strstr(gameWindowText, "Sonic 1") || forceSonic1) {423#else424if (strstr(gameWindowText, "Sonic 1")) {425#endif426gameType = GAME_SONIC1;427}428#endif429430#if !RETRO_USE_ORIGINAL_CODE431bool skipStore = skipStartMenu;432skipStartMenu = skipStart;433InitNativeObjectSystem();434skipStartMenu = skipStore;435#else436InitNativeObjectSystem();437#endif438439#if !RETRO_USE_ORIGINAL_CODE440// Calculate Skip frame441int lower = GetLowerRate(targetRefreshRate, refreshRate);442renderFrameIndex = targetRefreshRate / lower;443skipFrameIndex = refreshRate / lower;444445ReadSaveRAMData();446447if (Engine.gameType == GAME_SONIC1) {448AddAchievement("Ramp Ring Acrobatics",449"Without touching the ground,\rcollect all the rings in a\rtrapezoid formation in Green\rHill Zone Act 1");450AddAchievement("Blast Processing", "Clear Green Hill Zone Act 1\rin under 30 seconds");451AddAchievement("Secret of Marble Zone", "Travel though a secret\rroom in Marble Zone Act 3");452AddAchievement("Block Buster", "Break 16 blocks in a row\rwithout stopping");453AddAchievement("Ring King", "Collect 200 Rings");454AddAchievement("Secret of Labyrinth Zone", "Activate and ride the\rhidden platform in\rLabyrinth Zone Act 1");455AddAchievement("Flawless Pursuit", "Clear the boss in Labyrinth\rZone without getting hurt");456AddAchievement("Bombs Away", "Defeat the boss in Starlight Zone\rusing only the see-saw bombs");457AddAchievement("Hidden Transporter", "Collect 50 Rings and take the hidden transporter path\rin Scrap Brain Act 2");458AddAchievement("Chaos Connoisseur", "Collect all the chaos\remeralds");459AddAchievement("One For the Road", "As a parting gift, land a\rfinal hit on Dr. Eggman's\rescaping Egg Mobile");460AddAchievement("Beat The Clock", "Clear the Time Attack\rmode in less than 45\rminutes");461}462else if (Engine.gameType == GAME_SONIC2) {463AddAchievement("Quick Run", "Complete Emerald Hill\rZone Act 1 in under 35\rseconds");464AddAchievement("100% Chemical Free", "Complete Chemical Plant\rwithout going underwater");465AddAchievement("Early Bird Special", "Collect all the Chaos\rEmeralds before Chemical\rPlant");466AddAchievement("Superstar", "Complete any Act as\rSuper Sonic");467AddAchievement("Hit it Big", "Get a jackpot on the Casino Night slot machines");468AddAchievement("Bop Non-stop", "Defeat any boss in 8\rconsecutive hits without\rtouching he ground");469AddAchievement("Perfectionist", "Get a Perfect Bonus by\rcollecting every Ring in an\rAct");470AddAchievement("A Secret Revealed", "Find and complete\rHidden Palace Zone");471AddAchievement("Head 2 Head", "Win a 2P Versus race\ragainst a friend");472AddAchievement("Metropolis Master", "Complete Any Metropolis\rZone Act without getting\rhurt");473AddAchievement("Scrambled Egg", "Defeat Dr. Eggman's Boss\rAttack mode in under 7\rminutes");474AddAchievement("Beat the Clock", "Complete the Time Attack\rmode in less than 45\rminutes");475}476477if (skipStart)478Engine.gameMode = ENGINE_MAINGAME;479else480Engine.gameMode = ENGINE_WAIT;481482// "error message"483if (!running) {484char rootDir[0x80];485char pathBuffer[0x80];486487#if RETRO_PLATFORM == RETRO_UWP488if (!usingCWD)489sprintf(rootDir, "%s/", getResourcesPath());490else491sprintf(rootDir, "%s", "");492#elif RETRO_PLATFORM == RETRO_OSX493sprintf(rootDir, "%s/", gamePath);494#else495sprintf(rootDir, "%s", "");496#endif497sprintf(pathBuffer, "%s%s", rootDir, "usage.txt");498499FileIO *f;500if ((f = fOpen(pathBuffer, "w")) == NULL) {501PrintLog("ERROR: Couldn't open file '%s' for writing!", "usage.txt");502return;503}504505char textBuf[0x100];506sprintf(textBuf, "RETRO ENGINE v4 USAGE:\n");507fWrite(textBuf, 1, strlen(textBuf), f);508509sprintf(textBuf, "- Open the asset directory '%s' in a file browser\n", !rootDir[0] ? "./" : rootDir);510fWrite(textBuf, 1, strlen(textBuf), f);511512sprintf(textBuf, "- Place a data pack named '%s' in the asset directory\n", Engine.dataFile[0]);513fWrite(textBuf, 1, strlen(textBuf), f);514515sprintf(textBuf, "- OR extract a data pack and place the \"Data\" & \"Bytecode\" folders in the asset directory\n");516fWrite(textBuf, 1, strlen(textBuf), f);517518fClose(f);519}520521#endif522}523524void RetroEngine::Run()525{526Engine.deltaTime = 0.0f;527528unsigned long long targetFreq = SDL_GetPerformanceFrequency() / Engine.refreshRate;529unsigned long long curTicks = 0;530unsigned long long prevTicks = 0;531532while (running) {533#if !RETRO_USE_ORIGINAL_CODE534if (!vsync) {535curTicks = SDL_GetPerformanceCounter();536if (curTicks < prevTicks + targetFreq)537continue;538prevTicks = curTicks;539}540541Engine.deltaTime = 1.0 / 60;542#endif543running = ProcessEvents();544545// Focus Checks546if (!(disableFocusPause & 2)) {547if (!Engine.hasFocus) {548if (!(Engine.focusState & 1))549Engine.focusState = PauseSound() ? 3 : 1;550}551else if (Engine.focusState) {552if ((Engine.focusState & 2))553ResumeSound();554Engine.focusState = 0;555}556}557558if (!(Engine.focusState & 1) || vsPlaying) {559#if !RETRO_USE_ORIGINAL_CODE560for (int s = 0; s < gameSpeed; ++s) {561ProcessInput();562#endif563564#if !RETRO_USE_ORIGINAL_CODE565if (!masterPaused || frameStep) {566#endif567ProcessNativeObjects();568#if !RETRO_USE_ORIGINAL_CODE569}570#endif571}572573#if !RETRO_USE_ORIGINAL_CODE574if (!masterPaused || frameStep) {575#endif576FlipScreen();577578#if !RETRO_USE_ORIGINAL_CODE579#if RETRO_USING_OPENGL && RETRO_USING_SDL2580SDL_GL_SwapWindow(Engine.window);581#endif582frameStep = false;583}584#endif585586#if RETRO_REV00587Engine.message = MESSAGE_NONE;588#endif589590#if RETRO_USE_HAPTICS591int hapticID = GetHapticEffectNum();592if (hapticID >= 0) {593// playHaptics(hapticID);594}595else if (hapticID == HAPTIC_STOP) {596// stopHaptics();597}598#endif599}600}601602ReleaseAudioDevice();603ReleaseRenderDevice();604#if !RETRO_USE_ORIGINAL_CODE605ReleaseInputDevices();606#if RETRO_USE_NETWORKING607DisconnectNetwork(true);608#endif609WriteSettings();610#if RETRO_USE_MOD_LOADER611SaveMods();612#endif613#endif614615#if RETRO_USING_SDL1 || RETRO_USING_SDL2616SDL_Quit();617#endif618}619620#if RETRO_USE_MOD_LOADER621const tinyxml2::XMLElement *FirstXMLChildElement(tinyxml2::XMLDocument *doc, const tinyxml2::XMLElement *elementPtr, const char *name)622{623if (doc) {624if (!elementPtr)625return doc->FirstChildElement(name);626else627return elementPtr->FirstChildElement(name);628}629return NULL;630}631632const tinyxml2::XMLElement *NextXMLSiblingElement(tinyxml2::XMLDocument *doc, const tinyxml2::XMLElement *elementPtr, const char *name)633{634if (doc) {635if (!elementPtr)636return doc->NextSiblingElement(name);637else638return elementPtr->NextSiblingElement(name);639}640return NULL;641}642643const tinyxml2::XMLAttribute *FindXMLAttribute(const tinyxml2::XMLElement *elementPtr, const char *name) { return elementPtr->FindAttribute(name); }644const char *GetXMLAttributeName(const tinyxml2::XMLAttribute *attributePtr) { return attributePtr->Name(); }645int GetXMLAttributeValueInt(const tinyxml2::XMLAttribute *attributePtr) { return attributePtr->IntValue(); }646bool GetXMLAttributeValueBool(const tinyxml2::XMLAttribute *attributePtr) { return attributePtr->BoolValue(); }647const char *GetXMLAttributeValueString(const tinyxml2::XMLAttribute *attributePtr) { return attributePtr->Value(); }648649void RetroEngine::LoadXMLWindowText()650{651FileInfo info;652for (int m = 0; m < (int)modList.size(); ++m) {653if (!modList[m].active)654continue;655656SetActiveMod(m);657if (LoadFile("Data/Game/Game.xml", &info)) {658tinyxml2::XMLDocument *doc = new tinyxml2::XMLDocument;659660char *xmlData = new char[info.fileSize + 1];661FileRead(xmlData, info.fileSize);662xmlData[info.fileSize] = 0;663664bool success = doc->Parse(xmlData) == tinyxml2::XML_SUCCESS;665666if (success) {667const tinyxml2::XMLElement *gameElement = FirstXMLChildElement(doc, nullptr, "game");668const tinyxml2::XMLElement *titleElement = FirstXMLChildElement(doc, gameElement, "title");669if (titleElement) {670const tinyxml2::XMLAttribute *nameAttr = FindXMLAttribute(titleElement, "name");671if (nameAttr)672StrCopy(gameWindowText, GetXMLAttributeValueString(nameAttr));673}674}675676delete[] xmlData;677delete doc;678679CloseFile();680}681}682SetActiveMod(-1);683}684void RetroEngine::LoadXMLVariables()685{686FileInfo info;687for (int m = 0; m < (int)modList.size(); ++m) {688if (!modList[m].active)689continue;690691SetActiveMod(m);692if (LoadFile("Data/Game/Game.xml", &info)) {693tinyxml2::XMLDocument *doc = new tinyxml2::XMLDocument;694695char *xmlData = new char[info.fileSize + 1];696FileRead(xmlData, info.fileSize);697xmlData[info.fileSize] = 0;698699bool success = doc->Parse(xmlData) == tinyxml2::XML_SUCCESS;700701if (success) {702const tinyxml2::XMLElement *gameElement = FirstXMLChildElement(doc, nullptr, "game");703const tinyxml2::XMLElement *variablesElement = FirstXMLChildElement(doc, gameElement, "variables");704if (variablesElement) {705const tinyxml2::XMLElement *varElement = FirstXMLChildElement(doc, variablesElement, "variable");706if (varElement) {707do {708const tinyxml2::XMLAttribute *nameAttr = FindXMLAttribute(varElement, "name");709const char *varName = "unknownVariable";710if (nameAttr)711varName = GetXMLAttributeValueString(nameAttr);712713const tinyxml2::XMLAttribute *valAttr = FindXMLAttribute(varElement, "value");714int varValue = 0;715if (valAttr)716varValue = GetXMLAttributeValueInt(valAttr);717718if (globalVariablesCount >= GLOBALVAR_COUNT)719PrintLog("Failed to add global variable '%s' (max limit reached)", varName);720else if (GetGlobalVariableID(varName) == 0xFF) {721StrCopy(globalVariableNames[globalVariablesCount], varName);722globalVariables[globalVariablesCount] = varValue;723globalVariablesCount++;724}725} while ((varElement = NextXMLSiblingElement(doc, varElement, "variable")));726}727}728}729730delete[] xmlData;731delete doc;732733CloseFile();734}735}736SetActiveMod(-1);737}738void RetroEngine::LoadXMLPalettes()739{740FileInfo info;741for (int m = 0; m < (int)modList.size(); ++m) {742if (!modList[m].active)743continue;744745SetActiveMod(m);746if (LoadFile("Data/Game/Game.xml", &info)) {747tinyxml2::XMLDocument *doc = new tinyxml2::XMLDocument;748749char *xmlData = new char[info.fileSize + 1];750FileRead(xmlData, info.fileSize);751xmlData[info.fileSize] = 0;752753bool success = doc->Parse(xmlData) == tinyxml2::XML_SUCCESS;754755if (success) {756const tinyxml2::XMLElement *gameElement = FirstXMLChildElement(doc, nullptr, "game");757const tinyxml2::XMLElement *paletteElement = FirstXMLChildElement(doc, gameElement, "palette");758if (paletteElement) {759for (const tinyxml2::XMLElement *clrElement = paletteElement->FirstChildElement("color"); clrElement;760clrElement = clrElement->NextSiblingElement("color")) {761const tinyxml2::XMLAttribute *bankAttr = clrElement->FindAttribute("bank");762int clrBank = 0;763if (bankAttr)764clrBank = bankAttr->IntValue();765766const tinyxml2::XMLAttribute *indAttr = clrElement->FindAttribute("index");767int clrInd = 0;768if (indAttr)769clrInd = indAttr->IntValue();770771const tinyxml2::XMLAttribute *rAttr = clrElement->FindAttribute("r");772int clrR = 0;773if (rAttr)774clrR = rAttr->IntValue();775776const tinyxml2::XMLAttribute *gAttr = clrElement->FindAttribute("g");777int clrG = 0;778if (gAttr)779clrG = gAttr->IntValue();780781const tinyxml2::XMLAttribute *bAttr = clrElement->FindAttribute("b");782int clrB = 0;783if (bAttr)784clrB = bAttr->IntValue();785786SetPaletteEntry(clrBank, clrInd, clrR, clrG, clrB);787}788789for (const tinyxml2::XMLElement *clrsElement = paletteElement->FirstChildElement("colors"); clrsElement;790clrsElement = clrsElement->NextSiblingElement("colors")) {791const tinyxml2::XMLAttribute *bankAttr = clrsElement->FindAttribute("bank");792int bank = 0;793if (bankAttr)794bank = bankAttr->IntValue();795796const tinyxml2::XMLAttribute *indAttr = clrsElement->FindAttribute("start");797int index = 0;798if (indAttr)799index = indAttr->IntValue();800801std::string text = clrsElement->GetText();802// working: AABBFF #FFaaFF (12, 32, 34) (145 53 234)803std::regex search(R"((?:#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2}))|(?:\((\d+),?\s*(\d+),?\s*(\d+)\)))",804std::regex_constants::icase | std::regex_constants::ECMAScript);805std::smatch match;806while (std::regex_search(text, match, search)) {807int r, g, b;808int base, start;809if (match[1].matched) {810// we have hex811base = 16;812start = 1;813}814else {815// triplet816base = 10;817start = 4;818}819820r = std::stoi(match[start + 0].str(), nullptr, base);821g = std::stoi(match[start + 1].str(), nullptr, base);822b = std::stoi(match[start + 2].str(), nullptr, base);823824SetPaletteEntry(bank, index++, r, g, b);825text = match.suffix();826}827}828}829}830831delete[] xmlData;832delete doc;833834CloseFile();835}836}837SetActiveMod(-1);838}839void RetroEngine::LoadXMLObjects()840{841FileInfo info;842modObjCount = 0;843844for (int m = 0; m < (int)modList.size(); ++m) {845if (!modList[m].active)846continue;847848SetActiveMod(m);849if (LoadFile("Data/Game/Game.xml", &info)) {850tinyxml2::XMLDocument *doc = new tinyxml2::XMLDocument;851852char *xmlData = new char[info.fileSize + 1];853FileRead(xmlData, info.fileSize);854xmlData[info.fileSize] = 0;855856bool success = doc->Parse(xmlData) == tinyxml2::XML_SUCCESS;857858if (success) {859const tinyxml2::XMLElement *gameElement = FirstXMLChildElement(doc, nullptr, "game");860const tinyxml2::XMLElement *objectsElement = FirstXMLChildElement(doc, gameElement, "objects");861if (objectsElement) {862const tinyxml2::XMLElement *objElement = FirstXMLChildElement(doc, objectsElement, "object");863if (objElement) {864do {865const tinyxml2::XMLAttribute *nameAttr = FindXMLAttribute(objElement, "name");866const char *objName = "unknownObject";867if (nameAttr)868objName = GetXMLAttributeValueString(nameAttr);869870const tinyxml2::XMLAttribute *scrAttr = FindXMLAttribute(objElement, "script");871const char *objScript = "unknownObject.txt";872if (scrAttr)873objScript = GetXMLAttributeValueString(scrAttr);874875byte flags = 0;876877// forces the object to be loaded, this means the object doesn't have to be and *SHOULD NOT* be in the stage object list878// if it is, it'll cause issues!!!!879const tinyxml2::XMLAttribute *loadAttr = FindXMLAttribute(objElement, "forceLoad");880int objForceLoad = false;881if (loadAttr)882objForceLoad = GetXMLAttributeValueBool(loadAttr);883884flags |= (objForceLoad & 1);885886StrCopy(modTypeNames[modObjCount], objName);887StrCopy(modScriptPaths[modObjCount], objScript);888modScriptFlags[modObjCount] = flags;889modObjCount++;890891} while ((objElement = NextXMLSiblingElement(doc, objElement, "object")));892}893}894}895else {896PrintLog("Failed to parse game.xml File!");897}898899delete[] xmlData;900delete doc;901902CloseFile();903}904}905SetActiveMod(-1);906}907void RetroEngine::LoadXMLSoundFX()908{909FileInfo info;910FileInfo infoStore;911for (int m = 0; m < (int)modList.size(); ++m) {912if (!modList[m].active)913continue;914915SetActiveMod(m);916if (LoadFile("Data/Game/Game.xml", &info)) {917tinyxml2::XMLDocument *doc = new tinyxml2::XMLDocument;918919char *xmlData = new char[info.fileSize + 1];920FileRead(xmlData, info.fileSize);921xmlData[info.fileSize] = 0;922923bool success = doc->Parse(xmlData) == tinyxml2::XML_SUCCESS;924925if (success) {926const tinyxml2::XMLElement *gameElement = FirstXMLChildElement(doc, nullptr, "game");927const tinyxml2::XMLElement *soundsElement = FirstXMLChildElement(doc, gameElement, "sounds");928if (soundsElement) {929const tinyxml2::XMLElement *sfxElement = FirstXMLChildElement(doc, soundsElement, "soundfx");930if (sfxElement) {931do {932const tinyxml2::XMLAttribute *nameAttr = FindXMLAttribute(sfxElement, "name");933const char *sfxName = "unknownSFX";934if (nameAttr)935sfxName = GetXMLAttributeValueString(nameAttr);936937const tinyxml2::XMLAttribute *valAttr = FindXMLAttribute(sfxElement, "path");938const char *sfxPath = "unknownSFX.wav";939if (valAttr)940sfxPath = GetXMLAttributeValueString(valAttr);941942SetSfxName(sfxName, globalSFXCount);943944GetFileInfo(&infoStore);945CloseFile();946LoadSfx((char *)sfxPath, globalSFXCount);947SetFileInfo(&infoStore);948globalSFXCount++;949950} while ((sfxElement = NextXMLSiblingElement(doc, sfxElement, "soundfx")));951}952}953}954else {955PrintLog("Failed to parse game.xml File!");956}957958delete[] xmlData;959delete doc;960961CloseFile();962}963}964SetActiveMod(-1);965}966void RetroEngine::LoadXMLPlayers(TextMenu *menu)967{968FileInfo info;969970for (int m = 0; m < (int)modList.size(); ++m) {971if (!modList[m].active)972continue;973974SetActiveMod(m);975if (LoadFile("Data/Game/Game.xml", &info)) {976tinyxml2::XMLDocument *doc = new tinyxml2::XMLDocument;977978char *xmlData = new char[info.fileSize + 1];979FileRead(xmlData, info.fileSize);980xmlData[info.fileSize] = 0;981982bool success = doc->Parse(xmlData) == tinyxml2::XML_SUCCESS;983984if (success) {985const tinyxml2::XMLElement *gameElement = FirstXMLChildElement(doc, nullptr, "game");986const tinyxml2::XMLElement *playersElement = FirstXMLChildElement(doc, gameElement, "players");987if (playersElement) {988const tinyxml2::XMLElement *plrElement = FirstXMLChildElement(doc, playersElement, "player");989if (plrElement) {990do {991const tinyxml2::XMLAttribute *nameAttr = FindXMLAttribute(plrElement, "name");992const char *plrName = "unknownPlayer";993if (nameAttr)994plrName = GetXMLAttributeValueString(nameAttr);995996if (playerCount >= PLAYER_COUNT)997PrintLog("Failed to add dev menu character '%s' (max limit reached)", plrName);998else if (menu)999AddTextMenuEntry(menu, plrName);1000else1001StrCopy(playerNames[playerCount++], plrName);10021003} while ((plrElement = NextXMLSiblingElement(doc, plrElement, "player")));1004}1005}1006}1007else {1008PrintLog("Failed to parse game.xml File!");1009}10101011delete[] xmlData;1012delete doc;10131014CloseFile();1015}1016}1017SetActiveMod(-1);1018}1019void RetroEngine::LoadXMLStages(TextMenu *menu, int listNo)1020{1021FileInfo info;1022for (int m = 0; m < (int)modList.size(); ++m) {1023if (!modList[m].active)1024continue;10251026SetActiveMod(m);1027if (LoadFile("Data/Game/Game.xml", &info)) {1028tinyxml2::XMLDocument *doc = new tinyxml2::XMLDocument;10291030char *xmlData = new char[info.fileSize + 1];1031FileRead(xmlData, info.fileSize);1032xmlData[info.fileSize] = 0;10331034bool success = doc->Parse(xmlData) == tinyxml2::XML_SUCCESS;10351036if (success) {1037const tinyxml2::XMLElement *gameElement = FirstXMLChildElement(doc, nullptr, "game");1038const char *elementNames[] = { "presentationStages", "regularStages", "bonusStages", "specialStages" };10391040for (int l = 0; l < STAGELIST_MAX; ++l) {1041const tinyxml2::XMLElement *listElement = FirstXMLChildElement(doc, gameElement, elementNames[l]);1042if (listElement) {1043const tinyxml2::XMLElement *stgElement = FirstXMLChildElement(doc, listElement, "stage");1044if (stgElement) {1045do {1046const tinyxml2::XMLAttribute *nameAttr = FindXMLAttribute(stgElement, "name");1047const char *stgName = "unknownStage";1048if (nameAttr)1049stgName = GetXMLAttributeValueString(nameAttr);10501051const tinyxml2::XMLAttribute *folderAttr = FindXMLAttribute(stgElement, "folder");1052const char *stgFolder = "unknownStageFolder";1053if (folderAttr)1054stgFolder = GetXMLAttributeValueString(folderAttr);10551056const tinyxml2::XMLAttribute *idAttr = FindXMLAttribute(stgElement, "id");1057const char *stgID = "unknownStageID";1058if (idAttr)1059stgID = GetXMLAttributeValueString(idAttr);10601061const tinyxml2::XMLAttribute *highlightAttr = FindXMLAttribute(stgElement, "highlight");1062bool stgHighlighted = false;1063if (highlightAttr)1064stgHighlighted = GetXMLAttributeValueBool(highlightAttr);10651066if (menu) {1067if (listNo == 3 || listNo == 4) {1068if ((listNo == 4 && l == 2) || (listNo == 3 && l == 3)) {1069AddTextMenuEntry(menu, stgName);1070menu->entryHighlight[menu->rowCount - 1] = stgHighlighted;1071}1072}1073else if (listNo == l + 1) {1074AddTextMenuEntry(menu, stgName);1075menu->entryHighlight[menu->rowCount - 1] = stgHighlighted;1076}1077}1078else {1079StrCopy(stageList[l][stageListCount[l]].name, stgName);1080StrCopy(stageList[l][stageListCount[l]].folder, stgFolder);1081StrCopy(stageList[l][stageListCount[l]].id, stgID);1082stageList[l][stageListCount[l]].highlighted = stgHighlighted;10831084stageListCount[l]++;1085}10861087} while ((stgElement = NextXMLSiblingElement(doc, stgElement, "stage")));1088}1089}1090}1091}1092else {1093PrintLog("Failed to parse game.xml File!");1094}10951096delete[] xmlData;1097delete doc;10981099CloseFile();1100}1101}1102SetActiveMod(-1);1103}1104#endif11051106bool RetroEngine::LoadGameConfig(const char *filePath)1107{1108FileInfo info;1109byte fileBuffer = 0;1110byte fileBuffer2 = 0;1111char strBuffer[0x40];1112StrCopy(gameWindowText, "Retro-Engine"); // this is the default window name11131114globalVariablesCount = 0;1115#if RETRO_USE_MOD_LOADER1116playerCount = 0;1117#endif11181119bool loaded = LoadFile(filePath, &info);1120if (loaded) {1121FileRead(&fileBuffer, 1);1122FileRead(gameWindowText, fileBuffer);1123gameWindowText[fileBuffer] = 0;11241125FileRead(&fileBuffer, 1);1126FileRead(gameDescriptionText, fileBuffer);1127gameDescriptionText[fileBuffer] = 0;11281129byte buf[3];1130for (int c = 0; c < 0x60; ++c) {1131FileRead(buf, 3);1132SetPaletteEntry(-1, c, buf[0], buf[1], buf[2]);1133}11341135// Read Obect Names1136byte objectCount = 0;1137FileRead(&objectCount, 1);1138for (byte o = 0; o < objectCount; ++o) {1139FileRead(&fileBuffer, 1);1140FileRead(&strBuffer, fileBuffer);1141}11421143// Read Script Paths1144for (byte s = 0; s < objectCount; ++s) {1145FileRead(&fileBuffer, 1);1146FileRead(&strBuffer, fileBuffer);1147}11481149byte varCount = 0;1150FileRead(&varCount, 1);1151globalVariablesCount = varCount;1152for (int v = 0; v < varCount; ++v) {1153// Read Variable Name1154FileRead(&fileBuffer, 1);1155FileRead(&globalVariableNames[v], fileBuffer);1156globalVariableNames[v][fileBuffer] = 0;11571158// Read Variable Value1159FileRead(&fileBuffer2, 1);1160globalVariables[v] = fileBuffer2 << 0;1161FileRead(&fileBuffer2, 1);1162globalVariables[v] += fileBuffer2 << 8;1163FileRead(&fileBuffer2, 1);1164globalVariables[v] += fileBuffer2 << 16;1165FileRead(&fileBuffer2, 1);1166globalVariables[v] += fileBuffer2 << 24;1167}11681169// Read SFX1170byte globalSFXCount = 0;1171FileRead(&globalSFXCount, 1);1172for (int s = 0; s < globalSFXCount; ++s) { // SFX Names1173FileRead(&fileBuffer, 1);1174FileRead(&strBuffer, fileBuffer);1175strBuffer[fileBuffer] = 0;1176}1177for (byte s = 0; s < globalSFXCount; ++s) { // SFX Paths1178FileRead(&fileBuffer, 1);1179FileRead(&strBuffer, fileBuffer);1180strBuffer[fileBuffer] = 0;1181}11821183// Read Player Names1184byte plrCount = 0;1185FileRead(&plrCount, 1);1186#if RETRO_USE_MOD_LOADER1187// Check for max player limit1188if (plrCount >= PLAYER_COUNT) {1189PrintLog("WARNING: GameConfig attempted to exceed the player limit, truncating to supported limit");1190plrCount = PLAYER_COUNT;1191}1192#endif1193for (byte p = 0; p < plrCount; ++p) {1194FileRead(&fileBuffer, 1);1195FileRead(&strBuffer, fileBuffer);11961197// needed for PlayerName[] stuff in scripts1198#if !RETRO_USE_ORIGINAL_CODE1199strBuffer[fileBuffer] = 0;1200StrCopy(playerNames[p], strBuffer);1201playerCount++;1202#endif1203}12041205for (byte c = 0; c < 4; ++c) {1206// Special Stages are stored as cat 2 in file, but cat 3 in game :(1207int cat = c;1208if (c == 2)1209cat = 3;1210else if (c == 3)1211cat = 2;1212stageListCount[cat] = 0;1213FileRead(&fileBuffer, 1);1214stageListCount[cat] = fileBuffer;1215for (byte s = 0; s < stageListCount[cat]; ++s) {12161217// Read Stage Folder1218FileRead(&fileBuffer, 1);1219FileRead(&stageList[cat][s].folder, fileBuffer);1220stageList[cat][s].folder[fileBuffer] = 0;12211222// Read Stage ID1223FileRead(&fileBuffer, 1);1224FileRead(&stageList[cat][s].id, fileBuffer);1225stageList[cat][s].id[fileBuffer] = 0;12261227// Read Stage Name1228FileRead(&fileBuffer, 1);1229FileRead(&stageList[cat][s].name, fileBuffer);1230stageList[cat][s].name[fileBuffer] = 0;12311232// Read Stage Mode1233FileRead(&fileBuffer, 1);1234stageList[cat][s].highlighted = fileBuffer;1235}1236}12371238CloseFile();12391240#if RETRO_USE_MOD_LOADER1241LoadXMLWindowText();1242LoadXMLVariables();1243LoadXMLPalettes();1244LoadXMLObjects();1245LoadXMLPlayers(NULL);1246LoadXMLStages(NULL, 0);12471248SetGlobalVariableByName("options.devMenuFlag", devMenu ? 1 : 0);1249SetGlobalVariableByName("engine.standalone", 1);1250#endif1251}12521253#if RETRO_REV031254SetGlobalVariableByName("game.hasPlusDLC", !RSDK_AUTOBUILD);1255#endif12561257// These need to be set every time its reloaded1258nativeFunctionCount = 0;1259AddNativeFunction("SetAchievement", SetAchievement);1260AddNativeFunction("SetLeaderboard", SetLeaderboard);1261#if RETRO_USE_HAPTICS1262AddNativeFunction("HapticEffect", HapticEffect);1263#endif1264AddNativeFunction("Connect2PVS", Connect2PVS);1265AddNativeFunction("Disconnect2PVS", Disconnect2PVS);1266AddNativeFunction("SendEntity", SendEntity);1267AddNativeFunction("SendValue", SendValue);1268AddNativeFunction("ReceiveEntity", ReceiveEntity);1269AddNativeFunction("ReceiveValue", ReceiveValue);1270AddNativeFunction("TransmitGlobal", TransmitGlobal);1271AddNativeFunction("ShowPromoPopup", ShowPromoPopup);12721273// Introduced in the Sega Forever versions of S1 (3.9.0) and S2 (1.7.0)1274AddNativeFunction("NativePlayerWaitingAds", NativePlayerWaitingAds);1275AddNativeFunction("NativeWaterPlayerWaitingAds", NativeWaterPlayerWaitingAds);12761277#if RETRO_REV031278AddNativeFunction("NotifyCallback", NotifyCallback);1279#endif12801281#if RETRO_USE_NETWORKING1282AddNativeFunction("SetNetworkGameName", SetNetworkGameName);1283#endif12841285#if RETRO_USE_MOD_LOADER1286AddNativeFunction("ExitGame", ExitGame);1287AddNativeFunction("FileExists", FileExists);1288AddNativeFunction("OpenModMenu", OpenModMenu); // Opens the dev menu-based mod menu incase you cant be bothered or smth1289AddNativeFunction("AddAchievement", AddGameAchievement);1290AddNativeFunction("SetAchievementDescription", SetAchievementDescription);1291AddNativeFunction("ClearAchievements", ClearAchievements);1292AddNativeFunction("GetAchievementCount", GetAchievementCount);1293AddNativeFunction("GetAchievement", GetAchievement);1294AddNativeFunction("GetAchievementName", GetAchievementName);1295AddNativeFunction("GetAchievementDescription", GetAchievementDescription);1296AddNativeFunction("GetScreenWidth", GetScreenWidth);1297AddNativeFunction("SetScreenWidth", SetScreenWidth);1298AddNativeFunction("GetWindowScale", GetWindowScale);1299AddNativeFunction("SetWindowScale", SetWindowScale);1300AddNativeFunction("GetWindowScaleMode", GetWindowScaleMode);1301AddNativeFunction("SetWindowScaleMode", SetWindowScaleMode);1302AddNativeFunction("GetWindowFullScreen", GetWindowFullScreen);1303AddNativeFunction("SetWindowFullScreen", SetWindowFullScreen);1304AddNativeFunction("GetWindowBorderless", GetWindowBorderless);1305AddNativeFunction("SetWindowBorderless", SetWindowBorderless);1306AddNativeFunction("GetWindowVSync", GetWindowVSync);1307AddNativeFunction("SetWindowVSync", SetWindowVSync);1308AddNativeFunction("ApplyWindowChanges", ApplyWindowChanges); // Refresh window after changing window options1309AddNativeFunction("GetModCount", GetModCount);1310AddNativeFunction("GetModName", GetModName);1311AddNativeFunction("GetModDescription", GetModDescription);1312AddNativeFunction("GetModAuthor", GetModAuthor);1313AddNativeFunction("GetModVersion", GetModVersion);1314AddNativeFunction("GetModActive", GetModActive);1315AddNativeFunction("SetModActive", SetModActive);1316AddNativeFunction("MoveMod", MoveMod);1317AddNativeFunction("RefreshEngine", RefreshEngine); // Reload engine after changing mod status1318#endif13191320#if !RETRO_USE_ORIGINAL_CODE1321if (strlen(Engine.startSceneFolder) && strlen(Engine.startSceneID)) {1322SceneInfo *scene = &stageList[STAGELIST_BONUS][0xFE]; // slot 0xFF is used for "none" startStage1323strcpy(scene->name, "_RSDK_SCENE");1324strcpy(scene->folder, Engine.startSceneFolder);1325strcpy(scene->id, Engine.startSceneID);1326startList_Game = STAGELIST_BONUS;1327startStage_Game = 0xFE;1328}13291330#if RETRO_REV031331Engine.usingOrigins = GetGlobalVariableID("game.playMode") != 0xFF;1332#endif1333#endif13341335return loaded;1336}133713381339