diff --git a/LunaDll/Defines.h b/LunaDll/Defines.h index 8dad15f7..24bef7af 100644 --- a/LunaDll/Defines.h +++ b/LunaDll/Defines.h @@ -194,6 +194,9 @@ enum NPCTransformationCause { #define GM_PLAYER_KEY_SEL 8 #define GM_PLAYER_KEY_STR 9 +#define GM_MAX_PLAYERS 200 +#define GM_MAX_CHARACTERS 5 + #define DEFMEM(name, type, addr) static auto& name = *(type*)(addr); \ static constexpr std::uintptr_t name ## _ADDR = addr; \ static auto name ## _POINTER = (type*)(addr); @@ -207,7 +210,7 @@ DEFMEM(GM_DO_SCREENSHOT, short, 0x00B2504C); DEFMEM(GM_CREDITS_MODE, WORD, 0x00B2C89C); DEFMEM(GM_EPISODE_MODE, WORD, 0x00B2C5B4); // 0xFFFF = leave current level DEFMEM(GM_LEVEL_MODE, WORD, 0x00B2C620); -DEFMEM(GM_TITLE_INTRO_MODE, WORD, 0x00B2C620) +DEFMEM(GM_TITLE_INTRO_MODE, WORD, 0x00B2C620); /* The modes work as followed: GM_CREDITS_MODE == -1 --> Credits @@ -254,6 +257,11 @@ DEFMEM(GM_PLAYERS_COUNT, WORD, 0x00B2595E); DEFMEM(GM_EDIT_PLAYERS_PTR, void*, 0x00CF74D8); // Editor Template player DEFMEM(GM_PLAYER_POS, Momentum*, 0x00B25148); +DEFMEM(GM_PLAYER_OWED_MOUNT_PTR, void*, 0x00B25180); // When a yoshi/boot is taken from the player this returns after going back to the world map +DEFMEM(GM_PLAYER_OWED_MOUNT_COUNT, WORD, 0x00B25178); +DEFMEM(GM_PLAYER_OWED_MOUNT_TYPE_PTR, void*, 0x00B2519C); +DEFMEM(GM_PLAYER_OWED_MOUNT_TYPE_COUNT, WORD, 0x00B25194); + // Star counting DEFMEM(GM_STAR_COUNT, WORD, 0x00B251E0); DEFMEM(GM_STARS_PTR, void*, 0x00B25714); @@ -289,6 +297,7 @@ DEFMEM(GM_KEYRELEASED, WORD, 0x00B2C884); // States DEFMEM(GM_FREEZWITCH_ACTIV, WORD, 0x00B2C8B4); DEFMEM(GM_PAUSE_OPEN, WORD, 0x00B250E2); +DEFMEM(GM_FORCED_CONTROLS, WORD, 0x00B2D712); DEFMEM(GM_CHEAT_MONEYTREE_HAVEMONEY, DWORD, 0x00B2C8BA); @@ -303,7 +312,7 @@ DEFMEM(GM_OVERWORLD_PTR, void*, 0x00B2C5C8); // Overworld Level Array DEFMEM(GM_LEVEL_COUNT, WORD, 0x00B25960); DEFMEM(GM_LEVEL_BASE, void*, 0x00B25994); - +DEFMEM(GM_OVERWORLD_CUR_LVL, WORD, 0x00B2C5D6); // Level related memory DEFMEM(GM_LVLFILENAME_PTR, VB6StrPtr, 0x00B2C5A4); // Lvl filename @@ -396,12 +405,14 @@ DEFMEM(GM_INPUTTYPE, short*, 0x00B250A0); DEFMEM(GM_INPUTSTR_BUF_PTR, VB6StrPtr, 0x00B2C898); // Saves -DEFMEM(GM_CUR_SAVE_SLOT, WORD, 0x00B2C62A); // 1 2 or 3 +DEFMEM(GM_CUR_SAVE_SLOT, WORD, 0x00B2C62A); // 1 2 or 3 +DEFMEM(GM_SAVE_PERCENTAGE_PTR, void*, 0x00B2C644); // Cheats DEFMEM(GM_PLAYER_INVULN, WORD, 0x00B2C8C0); // 0xFFFF = invuln DEFMEM(GM_PLAYER_INFJUMP, WORD, 0x00B2C8AC); // 0xFFFF = infinite jumps DEFMEM(GM_PLAYER_SHADOWSTAR,WORD, 0x00B2C8AA); // 0xFFFF = shadowstar +DEFMEM(GM_WORLD_UNLOCK, WORD, 0x00B2C8B0); // 0xFFFF = illparkwhereiwant DEFMEM(GM_CHEATED, WORD, 0x00B2C8C4); // 0xFFFF = cheated // Frame counter @@ -628,6 +639,12 @@ DEFMEM(GM_GAMETITLE_1, VB6StrPtr, 0x8BD869); DEFMEM(GM_GAMETITLE_2, VB6StrPtr, 0x8BE25A); DEFMEM(GM_GAMETITLE_3, VB6StrPtr, 0x96AF26); +// World Information +DEFMEM(GM_WORLD_NAME, VB6StrPtr, 0x00B2C624); +DEFMEM(GM_WORLD_INTRO_FILENAME, VB6StrPtr, 0x00B25724); +DEFMEM(GM_HUB_STYLED_EPISODE, WORD, 0x00B25728); +DEFMEM(GM_RESTART_ON_DEATH, WORD, 0x00B2572A); + ///////////////////// /// -Assembly- /// @@ -842,6 +859,7 @@ DEFMEM(IMP_vbaInputFile, void*, 0x00401158); // Ptr to __cdecl #define GF_UPDATE_BLOCK_ANIM 0x009E14B0 #define GF_CLEANUP_LEVEL 0x008DC6E0 +#define GF_CLEANUP_WORLD 0x008E2E40 #define GF_LOAD_LEVEL 0x008D8F40 #define GF_INIT_CAMERA 0x009502E0 #define GF_RENDER_INIT_SCREEN 0x00987DE0 @@ -938,6 +956,7 @@ static const auto native_renderLevel = (void(__stdcall *)(void))GF_RENDER_LEV static const auto native_updateBlockAnim = (void(__stdcall *)(void))GF_UPDATE_BLOCK_ANIM; static const auto native_cleanupLevel = (void(__stdcall *)(void))GF_CLEANUP_LEVEL; +static const auto native_cleanupWorld = (void(__stdcall *)(void))GF_CLEANUP_WORLD; static const auto native_loadLevel = (void(__stdcall *)(VB6StrPtr* /*path*/))GF_LOAD_LEVEL; static const auto native_initCamera = (void(__stdcall *)(void))GF_INIT_CAMERA; static const auto native_renderInitScreen = (void(__stdcall *)(void))GF_RENDER_INIT_SCREEN; diff --git a/LunaDll/EventStateMachine.cpp b/LunaDll/EventStateMachine.cpp index 11f3688f..118a3728 100644 --- a/LunaDll/EventStateMachine.cpp +++ b/LunaDll/EventStateMachine.cpp @@ -8,6 +8,7 @@ #include "Misc/LoadScreen.h" #include "Rendering/GL/GLEngineProxy.h" #include "SdlMusic/SdlMusPlayer.h" +#include "SMBXInternal/Reconstructed/EpisodeMain.h" #include "SMBXInternal/Variables.h" // Global instance @@ -193,7 +194,6 @@ void EventStateMachine::sendOnTick(void) { m_onTickReady = false; sendSimpleLuaEvent("onTick"); - m_onTickEndReady = true; } diff --git a/LunaDll/FileManager/SMBXFileManager.cpp b/LunaDll/FileManager/SMBXFileManager.cpp index 89b7c868..dc1faba2 100644 --- a/LunaDll/FileManager/SMBXFileManager.cpp +++ b/LunaDll/FileManager/SMBXFileManager.cpp @@ -22,6 +22,8 @@ #include "../SMBXInternal/Layer.h" #include "../SMBXInternal/SMBXEvents.h" +#include "../SMBXInternal/Reconstructed/EpisodeMain.h" + #include SMBXLevelFileBase::SMBXLevelFileBase() : diff --git a/LunaDll/GameConfig/GameAutostart.cpp b/LunaDll/GameConfig/GameAutostart.cpp index bedb7e16..7724d737 100644 --- a/LunaDll/GameConfig/GameAutostart.cpp +++ b/LunaDll/GameConfig/GameAutostart.cpp @@ -187,6 +187,7 @@ GameAutostart GameAutostart::createGameAutostartByIniConfig(IniProcessing &reade autostarter.selectedWldPath = L""; autostarter.selectedEpisode = reader.value("episode-name", "").toString(); autostarter.singleplayer = reader.value("singleplayer", true).toBool(); + autostarter.playerCount = reader.value("players", 1).toInt(); autostarter.firstCharacter = static_cast(reader.value("character-player1", 1).toInt()); autostarter.secondCharacter = static_cast(reader.value("character-player2", 2).toInt()); autostarter.saveSlot = reader.value("save-slot", 1).toInt(); @@ -200,12 +201,26 @@ GameAutostart GameAutostart::createGameAutostartByStartupEpisodeSettings(const S autostarter.selectedWldPath = settings.wldPath; autostarter.selectedEpisode = ""; autostarter.singleplayer = (settings.players == 1); + autostarter.playerCount = settings.players; autostarter.firstCharacter = static_cast(settings.character1); autostarter.secondCharacter = static_cast(settings.character2); autostarter.saveSlot = settings.saveSlot; return autostarter; } +GameAutostart GameAutostart::createGameAutostartByManualSettings(std::wstring wldPath, int players, int character1, int character2, int saveSlot) +{ + GameAutostart autostarter; + autostarter.selectedWldPath = wldPath; + autostarter.selectedEpisode = ""; + autostarter.singleplayer = (players == 1); + autostarter.playerCount = players; + autostarter.firstCharacter = static_cast(character1); + autostarter.secondCharacter = static_cast(character2); + autostarter.saveSlot = saveSlot; + return autostarter; +} + /*static*/ void GameAutostart::ClearAutostartPatch() { skipIntoPatch.Unapply(); diff --git a/LunaDll/GameConfig/GameAutostart.h b/LunaDll/GameConfig/GameAutostart.h index 6d138e9d..8141af97 100644 --- a/LunaDll/GameConfig/GameAutostart.h +++ b/LunaDll/GameConfig/GameAutostart.h @@ -14,6 +14,7 @@ class GameAutostart std::string selectedEpisode; std::wstring selectedWldPath; bool singleplayer; + int playerCount; Characters firstCharacter; Characters secondCharacter; int saveSlot; @@ -23,10 +24,41 @@ class GameAutostart ~GameAutostart(); static GameAutostart createGameAutostartByIniConfig(IniProcessing& reader); static GameAutostart createGameAutostartByStartupEpisodeSettings(const StartupEpisodeSettings& settings); + static GameAutostart createGameAutostartByManualSettings(std::wstring wldPath, int players, int character1, int character2, int saveSlot); static void ClearAutostartPatch(); - void setSelectedEpisode(std::string val) { selectedEpisode = val; } - void setSaveSlot(int val) { saveSlot = val; } + void setSelectedEpisode(std::string val) + { + selectedEpisode = val; + } + void setSelectedEpisodePath(std::wstring val) + { + selectedWldPath = val; + } + void setPlayerCount(int val) + { + playerCount = val; + if(val >= 2) + { + singleplayer = false; + } + else if(val <= 1) + { + singleplayer = true; + } + } + void setFirstCharacter(int val) + { + firstCharacter = (Characters)val; + } + void setSecondCharacter(int val) + { + secondCharacter = (Characters)val; + } + void setSaveSlot(int val) + { + saveSlot = val; + } bool applyAutostart(); }; diff --git a/LunaDll/GlobalFuncs.cpp b/LunaDll/GlobalFuncs.cpp index 25fac006..405c3046 100644 --- a/LunaDll/GlobalFuncs.cpp +++ b/LunaDll/GlobalFuncs.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "Misc/MiscFuncs.h" #include "Input/Input.h" @@ -26,8 +27,13 @@ #include "SMBXInternal/Blocks.h" #include "SMBXInternal/NPCs.h" #include "Misc/RuntimeHook.h" +#include "Defines.h" #include "Misc/LoadScreen.h" +#include "SMBXInternal/Types.h" +#include "SMBXInternal/Variables.h" +#include "SMBXInternal/Functions.h" + void splitStr(std::vector& dest, const std::string& str, const char* separator) { dest.clear(); @@ -674,7 +680,6 @@ std::string resolveIfNotAbsolutePath(std::string filename) { return filename; } - std::string generateTimestamp(std::string format) { std::time_t t = std::time(NULL); @@ -799,6 +804,8 @@ std::wstring getCustomFolderPath() return full_path; } + + std::wstring getLatestFile(const std::initializer_list& paths) { FILETIME newest = { 0 }; @@ -1041,6 +1048,7 @@ std::string GetEditorPlacedItem() return (std::string)gEditorPlacedItem; } + namespace LunaMsgBox { static thread_local volatile uintptr_t s_activeCount = 0; @@ -1066,3 +1074,148 @@ namespace LunaMsgBox return (s_activeCount != 0); } } + + + + +std::string splitPathFromFilename(std::string str) +{ + std::string finalStr = str.substr(str.find_last_of("/\\") + 1); + return finalStr; +} + +std::string splitFilenameFromPath(std::string str) +{ + std::string finalStr = str.substr(0, str.find_last_of("/\\")); + return finalStr; +} + +std::string replaceFowardSlashesWithBackSlashes(std::string str) +{ + replaceSubStr(str, "/", "\\"); + return str; +} + +bool checkIfWorldIsInAppPath(std::string worldPath) +{ + if(!worldPath.find(gAppPathUTF8)) + { + return true; + } + else + { + return false; + } +} + +bool checkIfWorldIsInWorldPath(std::string worldPath) +{ + std::string appPath = gAppPathUTF8 + "\\worlds"; + if(!worldPath.find(appPath)) + { + return true; + } + else + { + return false; + } +} + +int findEpisodeIDFromWorldFileAndPath(std::string worldName) +{ + using namespace SMBX13; + + int id = 0; + for (int i = 0; i <= Vars::NumSelectWorld; i++) + { + auto& ep = Vars::SelectWorld[i + 1]; + if(worldName == std::string(ep.WorldPath) + std::string(ep.WorldFile)) + { + id = i + 1; + break; + } + } + return id; +} + +std::string findEpisodeWorldPathFromName(std::string name) +{ + using namespace SMBX13; + + std::string finalWldPath = ""; + + for (int i = 0; i <= Vars::NumSelectWorld; i++) + { + auto& ep = Vars::SelectWorld[i + 1]; + if(name == std::string(ep.WorldName)) + { + finalWldPath = std::string(ep.WorldPath) + std::string(ep.WorldFile); + break; + } + else + { + finalWldPath = ""; + } + } + return finalWldPath; +} + +std::string findNameFromEpisodeWorldPath(std::string wldPath) +{ + using namespace SMBX13; + + std::string finalName = ""; + + for (int i = 0; i <= Vars::NumSelectWorld; i++) + { + auto& ep = Vars::SelectWorld[i + 1]; + if(wldPath == std::string(ep.WorldPath) + std::string(ep.WorldFile)) + { + finalName = std::string(ep.WorldName); + break; + } + else + { + finalName = ""; + } + } + return finalName; +} + +int getUnblockedCharacterFromWorld(int curWorldID) +{ + using namespace SMBX13; + + int identity = 1; + + auto& ep = Vars::SelectWorld[curWorldID]; + + for (int i = 1; i <= 5; i++) + { + if(!ep.blockChar[i]) + { + identity = i; + break; + } + } + return identity; +} + + + +void checkBlockedCharacterFromWorldAndReplaceCharacterIfSo(int playerID) +{ + using namespace SMBX13; + + for (int i = 1; i <= 5; i++) + { + auto& p = Vars::Player[playerID]; + auto& ep = Vars::SelectWorld[Vars::selWorld]; + + if(ep.blockChar[i] && p.Character == i) + { + // if Player 1's character that was specified is blocked from the new episode, use the first character that isn't blocked + p.Character = getUnblockedCharacterFromWorld(Vars::selWorld); + } + } +} diff --git a/LunaDll/GlobalFuncs.h b/LunaDll/GlobalFuncs.h index 4f8090b2..164524e3 100644 --- a/LunaDll/GlobalFuncs.h +++ b/LunaDll/GlobalFuncs.h @@ -8,6 +8,8 @@ #include #include +#include "Defines.h" + //String manupulation things void splitStr(std::vector& dest, const std::string& str, const char* separator); void replaceSubStr(std::string& str, const std::string& from, const std::string& to); @@ -159,9 +161,29 @@ constexpr std::uint32_t DoubleMostSignificantDWord(double d) { std::string GetEditorPlacedItem(); +// World finding value functions +int findEpisodeIDFromWorldFileAndPath(std::string worldName); +std::string findEpisodeWorldPathFromName(std::string name); +std::string findNameFromEpisodeWorldPath(std::string wldPath); + +// Blocked character world functions +int getUnblockedCharacterFromWorld(int curWorldID); +void checkBlockedCharacterFromWorldAndReplaceCharacterIfSo(int playerID); + namespace LunaMsgBox { int ShowA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); int ShowW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType); bool IsActive(); } + +std::string splitPathFromFilename(std::string str); +std::string splitFilenameFromPath(std::string str); +std::string replaceFowardSlashesWithBackSlashes(std::string str); +bool checkIfWorldIsInAppPath(std::string worldPath); +bool checkIfWorldIsInWorldPath(std::string worldPath); + +extern void removeFilePathW(std::wstring &path); +extern void removeFilePathW(wchar_t*path, int length); +void removeFilePathA(std::string &path); +void removeFilePathA(char*path, int length); diff --git a/LunaDll/Globals.cpp b/LunaDll/Globals.cpp index effc97a0..5441138a 100644 --- a/LunaDll/Globals.cpp +++ b/LunaDll/Globals.cpp @@ -136,3 +136,6 @@ void printBoxA(const char *fmt, ...) std::string gEditorPlacedItem = "nil"; std::mutex g_editorIPCMutex; + +bool gEpisodeLoadedOnBoot = false; +Characters gPlayerStoredCharacters[] = {CHARACTER_MARIO,CHARACTER_MARIO,CHARACTER_MARIO,CHARACTER_MARIO }; diff --git a/LunaDll/Globals.h b/LunaDll/Globals.h index 45ca8188..0863f661 100644 --- a/LunaDll/Globals.h +++ b/LunaDll/Globals.h @@ -212,5 +212,10 @@ return; #endif +// Editor IPC Entities extern std::string gEditorPlacedItem; extern std::mutex g_editorIPCMutex; + +// Episode loading +extern Characters gPlayerStoredCharacters[]; +extern bool gEpisodeLoadedOnBoot; diff --git a/LunaDll/LuaMain/LuaProxy.h b/LunaDll/LuaMain/LuaProxy.h index 9a2af432..8a7ac417 100644 --- a/LunaDll/LuaMain/LuaProxy.h +++ b/LunaDll/LuaMain/LuaProxy.h @@ -909,7 +909,10 @@ namespace LuaProxy { void exitGame(); void exitEngine(); bool didGameOver(); - bool loadEpisode(const std::string& episodeName); + bool loadEpisode(std::string episodeName, int saveSlot, int numPlayers, int playerIDForOtherPlayers); + bool loadEpisode(std::string episodeName, int saveSlot, int numPlayers); + bool loadEpisode(std::string episodeName, int saveSlot); + bool loadEpisode(std::string episodeName); void pause(); void pause(bool atFrameEnd); void unpause(); @@ -994,6 +997,14 @@ namespace LuaProxy { //luabind::object getWorldData(lua_State *L); // TODO: Implement this once PGEFL will handle WorldMap reading luabind::object openNpcConfig(const std::string &filePath, lua_State *L); } + + namespace Episode{ + luabind::object list(lua_State *L); + int id(); + std::string name(); + std::string path(); + std::string filename(); + } //Non-Member-Constructors: RECT newRECT(); diff --git a/LunaDll/LuaMain/LuaProxyComponent/LuaProxyGlobalFunctions/LuaProxyGlobalFuncEpisode.cpp b/LunaDll/LuaMain/LuaProxyComponent/LuaProxyGlobalFunctions/LuaProxyGlobalFuncEpisode.cpp new file mode 100644 index 00000000..30540e0b --- /dev/null +++ b/LunaDll/LuaMain/LuaProxyComponent/LuaProxyGlobalFunctions/LuaProxyGlobalFuncEpisode.cpp @@ -0,0 +1,75 @@ +#include "../../LuaProxy.h" + +#include "../../../Defines.h" +#include "../../../GlobalFuncs.h" +#include "../../../Globals.h" + +#include "../../../Misc/VB6StrPtr.h" +#include "../../../Misc/VB6Bool.h" + +#include "../../../SMBXInternal/Types.h" +#include "../../../SMBXInternal/Variables.h" +#include "../../../SMBXInternal/Functions.h" + +#include "../../../SMBXInternal/Reconstructed/EpisodeMain.h" + +// luabind object that retrieves every episode from the world list, and creates a table from what was retrieved +static luabind::object getAllEpisodes(lua_State *L) +{ + using namespace SMBX13; + + luabind::object outData = luabind::newtable(L); + { + size_t counter = 0; + + for (int i = 1; i <= Vars::NumSelectWorld; i++) + { + luabind::object e = luabind::newtable(L); + auto& ep = Vars::SelectWorld[i]; + e["episodeName"] = (std::string)ep.WorldName; + e["episodePath"] = (std::string)ep.WorldPath; + e["episodeWorldFile"] = (std::string)ep.WorldFile; + outData[++counter] = e; + } + } + + return outData; +} + +// returns a table of all the episodes in the SMBX2 world list. If there's more than 100 episodes in the worlds directory, some episodes will be missing (Until the episode list system gets recoded, but Rednaxela suggested otherwise) +luabind::object LuaProxy::Episode::list(lua_State *L) +{ + return getAllEpisodes(L); +} + +// gets the currently running episode's ID, which can be used to retrieve the running episode's world info with Episode.list()[id] +int LuaProxy::Episode::id() +{ + using namespace SMBX13; + + return Vars::selWorld; +} + +std::string LuaProxy::Episode::name() +{ + using namespace SMBX13; + + auto& ep = Vars::SelectWorld[Vars::selWorld]; + return ep.WorldName; +} + +std::string LuaProxy::Episode::path() +{ + using namespace SMBX13; + + auto& ep = Vars::SelectWorld[Vars::selWorld]; + return ep.WorldPath; +} + +std::string LuaProxy::Episode::filename() +{ + using namespace SMBX13; + + auto& ep = Vars::SelectWorld[Vars::selWorld]; + return ep.WorldFile; +} diff --git a/LunaDll/LuaMain/LuaProxyComponent/LuaProxyGlobalFunctions/LuaProxyGlobalFuncLevel.cpp b/LunaDll/LuaMain/LuaProxyComponent/LuaProxyGlobalFunctions/LuaProxyGlobalFuncLevel.cpp index c98586d0..cf4e7a44 100644 --- a/LunaDll/LuaMain/LuaProxyComponent/LuaProxyGlobalFunctions/LuaProxyGlobalFuncLevel.cpp +++ b/LunaDll/LuaMain/LuaProxyComponent/LuaProxyGlobalFunctions/LuaProxyGlobalFuncLevel.cpp @@ -2,6 +2,8 @@ #include "../../../Defines.h" #include "../../../GlobalFuncs.h" +#include "../../../SMBXInternal/Reconstructed/EpisodeMain.h" + std::string LuaProxy::Level::filename() { return (std::string)GM_LVLFILENAME_PTR; diff --git a/LunaDll/LuaMain/LuaProxyComponent/LuaProxyGlobalFunctions/LuaProxyGlobalFuncMisc.cpp b/LunaDll/LuaMain/LuaProxyComponent/LuaProxyGlobalFunctions/LuaProxyGlobalFuncMisc.cpp index 5a31deae..25c1c96e 100644 --- a/LunaDll/LuaMain/LuaProxyComponent/LuaProxyGlobalFunctions/LuaProxyGlobalFuncMisc.cpp +++ b/LunaDll/LuaMain/LuaProxyComponent/LuaProxyGlobalFunctions/LuaProxyGlobalFuncMisc.cpp @@ -8,6 +8,10 @@ #include "../../../Misc/RuntimeHook.h" #include "../../../Misc/Gui/RichTextDialog.h" #include "../../../Misc/PerfTracker.h" +#include "../../../Misc/MiscFuncs.h" + +#include "../../../SMBXInternal/Reconstructed/EpisodeMain.h" +#include "../../../FileManager/SMBXFileManager.h" void LuaProxy::Misc::npcToCoins() { @@ -229,25 +233,79 @@ void LuaProxy::Misc::exitEngine() } bool luaDidGameOverFlag = false; + bool LuaProxy::Misc::didGameOver() { return luaDidGameOverFlag; } -bool LuaProxy::Misc::loadEpisode(const std::string& episodeName) +bool LuaProxy::Misc::loadEpisode(std::string episodeName, int saveSlot, int numPlayers, int playerIDForOtherPlayers) { - GameAutostart autoStartEpisode; - autoStartEpisode.setSelectedEpisode(episodeName); - autoStartEpisode.setSaveSlot(GM_CUR_SAVE_SLOT); - bool success = autoStartEpisode.applyAutostart(); - if (success) + bool success = false; + + Characters storedIdentity1 = Player::Get(1)->Identity; + Characters storedIdentity2; + + if(numPlayers > 1 && playerIDForOtherPlayers <= 0) + { + storedIdentity2 = Player::Get(2)->Identity; + } + else if(numPlayers <= 1) + { + storedIdentity2 = static_cast(1); + } + + if(playerIDForOtherPlayers > 0) + { + storedIdentity2 = static_cast(playerIDForOtherPlayers); + } + + // We're checking to see if the world path exists, and if it does, we can go through to load the episode + std::string worldPth = ""; + + if(fileExists(Str2WStr(episodeName))) + { + worldPth = episodeName; + success = true; + } + else + { + worldPth = findEpisodeWorldPathFromName(episodeName); + success = true; + } + + if(success) { - GM_EPISODE_MODE = 0; - GM_LEVEL_MODE = 0xFFFF; + gStartupSettings.epSettings.wldPath = Str2WStr(worldPth); + gStartupSettings.epSettings.players = numPlayers; + gStartupSettings.epSettings.character1 = static_cast(storedIdentity1); + gStartupSettings.epSettings.character2 = static_cast(storedIdentity2); + gStartupSettings.epSettings.saveSlot = saveSlot; + + GM_EPISODE_MODE = COMBOOL(false); + GM_CREDITS_MODE = COMBOOL(false); + GM_LEVEL_MODE = COMBOOL(true); } + return success; } +bool LuaProxy::Misc::loadEpisode(std::string episodeName, int saveSlot, int numPlayers) +{ + // default player id is 1 in this case + return LuaProxy::Misc::loadEpisode(episodeName, saveSlot, numPlayers, 1); +} + +bool LuaProxy::Misc::loadEpisode(std::string episodeName, int saveSlot) +{ + return LuaProxy::Misc::loadEpisode(episodeName, saveSlot, GM_PLAYERS_COUNT, 0); +} + +bool LuaProxy::Misc::loadEpisode(std::string episodeName) +{ + return LuaProxy::Misc::loadEpisode(episodeName, GM_CUR_SAVE_SLOT, GM_PLAYERS_COUNT, 0); +} + void LuaProxy::Misc::pause() { pause(false); @@ -350,4 +408,3 @@ luabind::object LuaProxy::Misc::__getPerfTrackerData(lua_State* L) } return retTable; } - diff --git a/LunaDll/LuaMain/LuaProxyComponent/LuaProxyWorld.cpp b/LunaDll/LuaMain/LuaProxyComponent/LuaProxyWorld.cpp index 44528798..8490c51b 100644 --- a/LunaDll/LuaMain/LuaProxyComponent/LuaProxyWorld.cpp +++ b/LunaDll/LuaMain/LuaProxyComponent/LuaProxyWorld.cpp @@ -1,6 +1,7 @@ #include "../LuaProxy.h" #include "../../SMBXInternal/Overworld.h" #include "../../Misc/MiscFuncs.h" +#include "../../SMBXInternal/Menu.h" LuaProxy::World::World() {} @@ -125,4 +126,3 @@ void LuaProxy::World::setPlayerPowerup(short playerPowerup) { SMBXOverworld::get()->currentPowerup = playerPowerup; } - diff --git a/LunaDll/LuaMain/LunaLuaMain.cpp b/LunaDll/LuaMain/LunaLuaMain.cpp index b09883d1..725fcfa7 100644 --- a/LunaDll/LuaMain/LunaLuaMain.cpp +++ b/LunaDll/LuaMain/LunaLuaMain.cpp @@ -33,6 +33,8 @@ #include "../Misc/CollisionMatrix.h" #include "../SMBXInternal/Ports.h" +#include "../FileManager/SMBXFileManager.h" + /*static*/ DWORD CLunaFFILock::currentLockTlsIdx = TlsAlloc(); extern bool luaDidGameOverFlag; @@ -715,7 +717,15 @@ void CLunaLua::bindAll() def("exitGame", &LuaProxy::Misc::exitGame), def("exitEngine", &LuaProxy::Misc::exitEngine), def("didGameOver", &LuaProxy::Misc::didGameOver), - def("loadEpisode", &LuaProxy::Misc::loadEpisode), + // Misc.loadEpisode + // Episode name only + def("loadEpisode", (bool(*)(std::string))&LuaProxy::Misc::loadEpisode), + // Episode name and save slot + def("loadEpisode", (bool(*)(std::string, int))&LuaProxy::Misc::loadEpisode), + // Episode name, save slot, and number of players + def("loadEpisode", (bool(*)(std::string, int, int))&LuaProxy::Misc::loadEpisode), + // Episode name, save slot, number of players, and other player IDs in case + def("loadEpisode", (bool(*)(std::string, int, int, int))&LuaProxy::Misc::loadEpisode), def("pause", (void(*)(void))&LuaProxy::Misc::pause), def("pause", (void(*)(bool))&LuaProxy::Misc::pause), def("unpause", &LuaProxy::Misc::unpause), @@ -971,6 +981,16 @@ void CLunaLua::bindAll() def("__getMuteForAlias", LuaProxy::Audio::__getMuteForAlias) ], /*************************Audio*end*************************/ + + namespace_("Episode")[ + def("list", &LuaProxy::Episode::list), + def("id", (int(*)())&LuaProxy::Episode::id), + + def("name", (std::string(*)())&LuaProxy::Episode::name), + def("path", (std::string(*)())&LuaProxy::Episode::path), + def("filename", (std::string(*)())&LuaProxy::Episode::filename) + + ], LUAHELPER_DEF_CLASS(NativeInputConfig) .def("__eq", LUAPROXY_DEFUSERDATAINEDXCOMPARE(LuaProxy::InputConfig, m_index)) @@ -1211,7 +1231,6 @@ void CLunaLua::bindAll() .property("filename", &LuaProxy::LevelObject::filename) .def("mem", static_cast(&LuaProxy::LevelObject::mem)) .def("mem", static_cast(&LuaProxy::LevelObject::mem)) - ]; } diff --git a/LunaDll/LunaDll.vcxproj b/LunaDll/LunaDll.vcxproj index 96d4ae12..2709925a 100644 --- a/LunaDll/LunaDll.vcxproj +++ b/LunaDll/LunaDll.vcxproj @@ -314,6 +314,7 @@ + @@ -475,6 +476,7 @@ + @@ -555,6 +557,7 @@ + diff --git a/LunaDll/LunaDll.vcxproj.filters b/LunaDll/LunaDll.vcxproj.filters index 0b0f6130..5dcafa6b 100644 --- a/LunaDll/LunaDll.vcxproj.filters +++ b/LunaDll/LunaDll.vcxproj.filters @@ -718,6 +718,9 @@ Misc\Gui + + SMBXInternal\Reconstructed + SMBXInternal @@ -993,6 +996,9 @@ LuaMain\LuaProxyComponent\LuaProxyGlobalFunctions + + LuaMain\LuaProxyComponent\LuaProxyGlobalFunctions + Misc\Gui @@ -1409,6 +1415,9 @@ libs\luabind-deboostified + + libs\luabind-deboostified + Rendering @@ -1490,6 +1499,9 @@ Misc + + SMBXInternal\Reconstructed + Misc diff --git a/LunaDll/Misc/MiscFuncs.cpp b/LunaDll/Misc/MiscFuncs.cpp index 82e4f371..9b1c0d78 100644 --- a/LunaDll/Misc/MiscFuncs.cpp +++ b/LunaDll/Misc/MiscFuncs.cpp @@ -9,6 +9,7 @@ #include "../SMBXInternal/PlayerMOB.h" #include "../SMBXInternal/Layer.h" #include "../Rendering/RenderOps/RenderStringOp.h" +#include "../GlobalFuncs.h" #ifdef __MINGW32__ std::wstring std::to_wstring(long long src) @@ -55,6 +56,12 @@ bool fileExists(const std::wstring &szPath) return (FileExists(szPath.c_str()) != FALSE); } +bool fileExists(std::string szPath) +{ + const std::wstring &szPathFinal = Str2WStr(szPath); + return (FileExists(szPathFinal.c_str()) != FALSE); +} + bool directoryExists(const std::wstring &szPath) { return (DirectoryExists(szPath.c_str()) != FALSE); diff --git a/LunaDll/Misc/MiscFuncs.h b/LunaDll/Misc/MiscFuncs.h index 1725b0e4..fac45bbf 100644 --- a/LunaDll/Misc/MiscFuncs.h +++ b/LunaDll/Misc/MiscFuncs.h @@ -40,6 +40,7 @@ BOOL FileExists(LPCTSTR szPath); BOOL DirectoryExists(LPCTSTR szPath); bool fileExists(const std::wstring &szPath); +bool fileExists(std::string szPath); bool directoryExists(const std::wstring &szPath); bool hasSuffix(const std::string &path, const std::string &suffix); diff --git a/LunaDll/Misc/RuntimeHook.h b/LunaDll/Misc/RuntimeHook.h index fc43829f..c4febe5c 100644 --- a/LunaDll/Misc/RuntimeHook.h +++ b/LunaDll/Misc/RuntimeHook.h @@ -124,6 +124,8 @@ extern void IsNPCCollidesWithVeggiHook_Wrapper(); extern void __stdcall runtimeHookCreditsLoop(); extern void __stdcall runtimeHookGameover(); +extern void __stdcall runtimeHookGameMenu(); + /************************************************************************/ /* Hooks for some rendering purposes */ /************************************************************************/ diff --git a/LunaDll/Misc/RuntimeHookComponents/RuntimeHookCharacterId.cpp b/LunaDll/Misc/RuntimeHookComponents/RuntimeHookCharacterId.cpp index adbd76ac..9e75505d 100644 --- a/LunaDll/Misc/RuntimeHookComponents/RuntimeHookCharacterId.cpp +++ b/LunaDll/Misc/RuntimeHookComponents/RuntimeHookCharacterId.cpp @@ -143,10 +143,11 @@ static bool runtimeHookCharacterIdApplied = false; #define DECL_HOOK(FUNCNAME, ARG, TAIL) __declspec(naked) static void __stdcall FUNCNAME() { ASM_ARG(ARG); ASM_TAIL_##TAIL(); } // Declare hook functions -DECL_HOOK(HOOK_0x8C0329, esi + 0xF0, MOV_ebx); -DECL_HOOK(HOOK_0x8C0362, esi + 0xF0, MOV_ebx); -DECL_HOOK(HOOK_0x8C0B35, edi + 0xF0, MOV_ebx); -DECL_HOOK(HOOK_0x8C0B6E, edi + 0xF0, MOV_ebx); +// Some hooks are no longer needed due to the new way to load episodes on boot +//DECL_HOOK(HOOK_0x8C0329, esi + 0xF0, MOV_ebx); +//DECL_HOOK(HOOK_0x8C0362, esi + 0xF0, MOV_ebx); +//DECL_HOOK(HOOK_0x8C0B35, edi + 0xF0, MOV_ebx); +//DECL_HOOK(HOOK_0x8C0B6E, edi + 0xF0, MOV_ebx); DECL_HOOK(HOOK_0x8D22B3, ecx + 0xF0, CMP_5); DECL_HOOK(HOOK_0x8D376C, edx + 0xF0, MOV_eax); DECL_HOOK(HOOK_0x8D3812, ecx + 0xF0, MOV_edx); @@ -647,10 +648,10 @@ DECL_HOOK(HOOK_0xA60BB9, ecx + eax * 4 + 0xF0, CMP_4); DECL_HOOK(HOOK_0xA60BE3, eax + edx * 4 + 0xF0, CMP_5); // Patches -static auto patch_0x8C0329 = PATCH(0x8C0329).CALL(HOOK_0x8C0329).NOP_PAD_TO_SIZE<7>(); -static auto patch_0x8C0362 = PATCH(0x8C0362).CALL(HOOK_0x8C0362).NOP_PAD_TO_SIZE<7>(); -static auto patch_0x8C0B35 = PATCH(0x8C0B35).CALL(HOOK_0x8C0B35).NOP_PAD_TO_SIZE<7>(); -static auto patch_0x8C0B6E = PATCH(0x8C0B6E).CALL(HOOK_0x8C0B6E).NOP_PAD_TO_SIZE<7>(); +//static auto patch_0x8C0329 = PATCH(0x8C0329).CALL(HOOK_0x8C0329).NOP_PAD_TO_SIZE<7>(); +//static auto patch_0x8C0362 = PATCH(0x8C0362).CALL(HOOK_0x8C0362).NOP_PAD_TO_SIZE<7>(); +//static auto patch_0x8C0B35 = PATCH(0x8C0B35).CALL(HOOK_0x8C0B35).NOP_PAD_TO_SIZE<7>(); +//static auto patch_0x8C0B6E = PATCH(0x8C0B6E).CALL(HOOK_0x8C0B6E).NOP_PAD_TO_SIZE<7>(); static auto patch_0x8D22B3 = PATCH(0x8D22B3).CALL(HOOK_0x8D22B3).NOP_PAD_TO_SIZE<8>(); static auto patch_0x8D376C = PATCH(0x8D376C).CALL(HOOK_0x8D376C).NOP_PAD_TO_SIZE<7>(); static auto patch_0x8D3812 = PATCH(0x8D3812).CALL(HOOK_0x8D3812).NOP_PAD_TO_SIZE<7>(); @@ -1460,10 +1461,10 @@ static auto patch_variant_effect_0x9E73C9 = PATCH(0x9E73C9).CALL(RunEffectRawHoo // Patch list static Patchable* runtimeHookCharacterIdPatchList[] = { - &patch_0x8C0329, - &patch_0x8C0362, - &patch_0x8C0B35, - &patch_0x8C0B6E, + //&patch_0x8C0329, + //&patch_0x8C0362, + //&patch_0x8C0B35, + //&patch_0x8C0B6E, &patch_0x8D22B3, &patch_0x8D376C, &patch_0x8D3812, @@ -2127,7 +2128,7 @@ PlayerMOB* getTemplateForCharacter(int id) return nullptr; } -static PlayerMOB* getTemplateForCharacterWithDummyFallback(int id) +PlayerMOB* getTemplateForCharacterWithDummyFallback(int id) { static PlayerMOB dummyPlayerStruct = {0}; diff --git a/LunaDll/Misc/RuntimeHookComponents/RuntimeHookGeneral.cpp b/LunaDll/Misc/RuntimeHookComponents/RuntimeHookGeneral.cpp index 0b6d0465..10f4a3f8 100644 --- a/LunaDll/Misc/RuntimeHookComponents/RuntimeHookGeneral.cpp +++ b/LunaDll/Misc/RuntimeHookComponents/RuntimeHookGeneral.cpp @@ -1410,7 +1410,9 @@ void TrySkipPatch() // fixes a credits bug that causes toad (or any otherwise shoe wearing player) to constantly hold jump, // related to some (typically unused?) logic that makes players jump in the credits, but the range is too low when hopping in shoe for some reason that doesn't matter in the base game PATCH(0x8F6E11).NOP_PAD_TO_SIZE<4>().Apply(); // effectively comments out line 9624 in modMain.bas, effectively increasing the range that blocks are checked for - + + PATCH(0x8C0763).SAFE_CALL(&runtimeHookGameMenu).JMP(0x8C11B1).Apply(); // The Game Menu + PATCH(0x9B7B80).CALL(&runtimeHookGameover).NOP_PAD_TO_SIZE<28>().Apply(); PATCH(0xB2F244).dword(reinterpret_cast(&mciSendStringHookA)).Apply(); @@ -1518,7 +1520,7 @@ void TrySkipPatch() PATCH(0x94D5E7).CALL(&GenerateScreenshotHook).Apply(); PATCH(0x8C03DC).CALL(&InitLevelEnvironmentHook).Apply(); - PATCH(0x8C0A1A).CALL(&InitLevelEnvironmentHook).Apply(); + //PATCH(0x8C0A1A).CALL(&InitLevelEnvironmentHook).Apply(); PATCH(0x8C1383).CALL(&InitLevelEnvironmentHook).Apply(); PATCH(0x8C1953).CALL(&InitLevelEnvironmentHook).Apply(); PATCH(0x8CE292).CALL(&InitLevelEnvironmentHook).Apply(); @@ -1628,7 +1630,7 @@ void TrySkipPatch() // These ones are normally not sensitive to the "max FPS" setting PATCH(0x8BFD4A).SAFE_CALL(frameTimingHookPtr).NOP_PAD_TO_SIZE<0x40>().Apply(); PATCH(0x8C0488).SAFE_CALL(frameTimingHookPtr).NOP_PAD_TO_SIZE<0x40>().Apply(); - PATCH(0x8C0EE6).SAFE_CALL(frameTimingHookPtr).NOP_PAD_TO_SIZE<0x40>().Apply(); + //PATCH(0x8C0EE6).SAFE_CALL(frameTimingHookPtr).NOP_PAD_TO_SIZE<0x40>().Apply(); // These ones are normally sensitive to the "max FPS" setting PATCH(0x8C15A7).SAFE_CALL(frameTimingMaxFPSHookPtr).NOP_PAD_TO_SIZE<0x4A>().Apply(); diff --git a/LunaDll/Misc/RuntimeHookComponents/RuntimeHookHooks.cpp b/LunaDll/Misc/RuntimeHookComponents/RuntimeHookHooks.cpp index 40c86dff..9d42305c 100644 --- a/LunaDll/Misc/RuntimeHookComponents/RuntimeHookHooks.cpp +++ b/LunaDll/Misc/RuntimeHookComponents/RuntimeHookHooks.cpp @@ -43,6 +43,9 @@ #include "../../Misc/VB6RNG.h" +#include "../../SMBXInternal/Reconstructed/EpisodeMain.h" +#include "../../SMBXInternal/Overworld.h" + void CheckIPCQuitRequest(); extern HHOOK HookWnd; @@ -184,6 +187,15 @@ extern int __stdcall LoadWorld() g_GLEngine.SetFirstFramePending(); } + if (!GM_CREDITS_MODE) + { + for (int i = 1; i <= min(GM_PLAYERS_COUNT, (WORD)4); i++) { + // store player characters at the time of level load, + // these are used to restore the character if the episode has to be reloaded, or another episode was launched + gPlayerStoredCharacters[i-1] = Player::Get(i)->Identity; + } + } + return GM_PLAYERS_COUNT; } @@ -200,7 +212,6 @@ __declspec(naked) void __stdcall LoadWorldHook(void) extern int __stdcall LoadIntro() { - if (!gAutostartRan && !gStartupSettings.waitForIPC && !TestModeIsEnabled()) { gAutostartRan = true; @@ -1601,16 +1612,101 @@ _declspec(naked) void __stdcall loadLevel_OrigFunc(VB6StrPtr* filename) } } -Characters playerStoredCharacters[] = {CHARACTER_MARIO,CHARACTER_MARIO,CHARACTER_MARIO,CHARACTER_MARIO }; +//If the legacy title screen is about to boot, prevent that and go straight to loading the episode +void __stdcall runtimeHookGameMenu() +{ + GM_LEVEL_MODE = 0; // Set this to prevent multiple loops + // Check to see if IPC is not waiting and Test Mode isn't enabled. If so, continue. + if(!gEpisodeLoadedOnBoot) + { + GameAutostart autostarter; + if(!gStartupSettings.waitForIPC && !TestModeIsEnabled()) + { + std::string autostartFile = WStr2Str(getLatestConfigFile(L"autostart.ini")); + + if(!gStartupSettings.epSettings.enabled && file_existsX(autostartFile)) + { + // Try reading the autostart.ini file first if there's no settings available + IniProcessing autostartConfig(autostartFile); + if (autostartConfig.beginGroup("autostart")) + { + bool doAutostart = autostartConfig.value("do-autostart", false).toBool(); + autostartConfig.endGroup(); + if (doAutostart) + { + // Note: Internally this uses beginGroup and endGroup, so the group won't be open after it + autostartConfig.beginGroup("autostart"); + + std::string selectedEpisode = autostartConfig.value("episode-name", "").toString(); + std::wstring selectedEpisodePath = Str2WStr(autostartConfig.value("episode-wld-file", "").toString()); + int playerCount = autostartConfig.value("players", 1).toInt(); + Characters firstCharacter = static_cast(autostartConfig.value("character-player1", 1).toInt()); + Characters secondCharacter = static_cast(autostartConfig.value("character-player2", 2).toInt()); + int saveSlot = autostartConfig.value("save-slot", 1).toInt(); + + autostarter.setSelectedEpisode(selectedEpisode); + + gEpisodeMain.LaunchEpisode(selectedEpisodePath, saveSlot, playerCount, firstCharacter, secondCharacter); + + if (autostartConfig.value("transient", false).toBool()) + { + remove(autostartFile.c_str()); + } + autostartConfig.endGroup(); + } + } + autostartConfig.endGroup(); + } + else if(gStartupSettings.epSettings.enabled && gStartupSettings.epSettings.wldPath != L"") + { + // If there's no autostart file but the command prompt gives out a world path and some other things, we will then boot to the episode from there + std::string selectedEpisode = ""; + std::wstring selectedEpisodePath = gStartupSettings.epSettings.wldPath; + int playerCount = gStartupSettings.epSettings.players; + Characters firstCharacter = static_cast(gStartupSettings.epSettings.character1); + Characters secondCharacter = static_cast(gStartupSettings.epSettings.character2); + int saveSlot = gStartupSettings.epSettings.saveSlot; + + autostarter.setSelectedEpisode(selectedEpisode); + + gEpisodeMain.LaunchEpisode(selectedEpisodePath, saveSlot, playerCount, firstCharacter, secondCharacter); + } + else + { + // If there's still nothing, we don't have any settings so we shouldn't continue booting LunaDLL + std::string msg = "There is no world file specified on starting LunaLua. This means that you booted LunaLoader.exe with no arguments regarding selecting a world or level. Please load a world or level starting SMBX2 by loading the X2 launcher (Or Command Prompt) instead."; + MessageBoxA(gMainWindowHwnd, msg.c_str(), "Error", MB_ICONWARNING | MB_OK); + _exit(0); + } + } + } + else if(gEpisodeLoadedOnBoot) + { + GameAutostart autostarter; + if(!gStartupSettings.waitForIPC && !TestModeIsEnabled() && gEpisodeLoadedOnBoot) + { + std::string selectedEpisode = ""; + std::wstring selectedEpisodePath = gStartupSettings.epSettings.wldPath; + int playerCount = gStartupSettings.epSettings.players; + Characters firstCharacter = static_cast(gStartupSettings.epSettings.character1); + Characters secondCharacter = static_cast(gStartupSettings.epSettings.character2); + int saveSlot = gStartupSettings.epSettings.saveSlot; + + autostarter.setSelectedEpisode(selectedEpisode); + + gEpisodeMain.LaunchEpisode(selectedEpisodePath, saveSlot, playerCount, firstCharacter, secondCharacter); + } + } +} void __stdcall runtimeHookLoadLevel(VB6StrPtr* filename) { - if (!GM_CREDITS_MODE) + if (GM_CREDITS_MODE == 0) { for (int i = 1; i <= min(GM_PLAYERS_COUNT, (WORD)4); i++) { // store player characters at the time of level load, // these are used to restore the character if the episode has to be reloaded - playerStoredCharacters[i-1] = Player::Get(i)->Identity; + gPlayerStoredCharacters[i-1] = Player::Get(i)->Identity; } } @@ -4979,7 +5075,7 @@ static void LunaLuaResetEpisode() { for (int i = 1; i <= GM_PLAYERS_COUNT; i++) { auto p = Player::Get(i); // restore this player's character - p->Identity = playerStoredCharacters[min(i, 4)-1]; + p->Identity = gPlayerStoredCharacters[min(i, 4)-1]; // apply saved template auto t = getTemplateForCharacter(p->Identity); if (t != nullptr) { diff --git a/LunaDll/SMBXInternal/Reconstructed/EpisodeMain.cpp b/LunaDll/SMBXInternal/Reconstructed/EpisodeMain.cpp new file mode 100644 index 00000000..7e357285 --- /dev/null +++ b/LunaDll/SMBXInternal/Reconstructed/EpisodeMain.cpp @@ -0,0 +1,665 @@ +#include +#include +#include +#include + +#include "EpisodeMain.h" + +#include "../../Globals.h" +#include "../../GlobalFuncs.h" + +#include "../../libs/PGE_File_Formats/file_formats.h" + +#include "../../LuaMain/LuaHelper.h" +#include "../../LuaMain/LuaProxy.h" +#include "../../LuaMain/LunaPathValidator.h" + +#include "../../GameConfig/GameAutostart.h" + +#include "../../Misc/LoadScreen.h" +#include "../../Misc/MiscFuncs.h" +#include "../../Misc/ResourceFileMapper.h" +#include "../../Misc/RuntimeHook.h" + +#include "../../FileManager/SMBXFileManager.h" + +#include "../Functions.h" +#include "../Types.h" +#include "../Variables.h" + +extern PlayerMOB* getTemplateForCharacterWithDummyFallback(int id); +extern "C" void __cdecl LunaLuaSetGameData(const char* dataPtr, int dataLen); + +EpisodeMain gEpisodeMain; + +EpisodeMain::EpisodeMain() +{} + +EpisodeMain::~EpisodeMain() {} + +// The big one. This will load an episode anywhere in the engine. This is also used when booting the engine. +void EpisodeMain::LaunchEpisode(std::wstring wldPathWS, int saveSlot, int playerCount, Characters firstCharacter, Characters secondCharacter) +{ + using namespace SMBX13; + + //--ElseIf .Jump = True Or .Start = True Or (GetKeyState(vbKeySpace) And KEY_PRESSED) Or (GetKeyState(vbKeyReturn) And KEY_PRESSED) Or MenuMouseClick = True Then (line 4945)-- + + // replace all the forward slashes with backward slashes + replaceSubStrW(wldPathWS, L"/", L"\\"); + + // this is used for setting up loadEpisode from lua after first booting an episode + GameAutostart GameAutostartFunc; + + // this needs to be set already so we can convert it to a std::string + std::wstring fullPathWS = wldPathWS; + + /* + -- + **string references** + + - world path with world file + - resolved world path with world file + - path with no world file + - world file with no path + - path with no world file, ending with a slash + -- + */ + + std::string wldPathS = WStr2Str(wldPathWS); + std::string fullPathS = WStr2Str(fullPathWS); + std::string fullPthNoWorldFileS = splitFilenameFromPath(fullPathS); + std::string fullWorldFileNoPthS = splitPathFromFilename(fullPathS); + std::string fullPthNoWorldFileWithEndSlashS = fullPthNoWorldFileS + "\\"; + + /* + -- + **wstring references** + + - resolved world path with world file (see above) + - path with no world file + - path with no world file, ending with a slash + -- + */ + + std::wstring fullPthNoWorldFileWS = Str2WStr(fullPthNoWorldFileS); + std::wstring fullPthNoWorldFileWithEndSlashWS = Str2WStr(fullPthNoWorldFileWithEndSlashS); + + /* + -- + **visual basic 6 string ptr references** + + - world path with world file + - world file with no path + - path with no world file, ending with a slash + - a blank string variable + -- + */ + + VB6StrPtr fullPathVB6 = fullPathS; + VB6StrPtr fullWorldFileNoPthVB6 = fullWorldFileNoPthS; + VB6StrPtr fullPthNoWorldFileWithEndSlashVB6 = fullPthNoWorldFileWithEndSlashS; + VB6StrPtr blankString = ""; + + // set FileNamePath, otherwise the Loadscreen won't have write access to the episode we're loading to + Vars::FileNamePath = fullPthNoWorldFileWithEndSlashWS; + + // FileFormats WorldData, saved for the FindSaves function and the world intro filename + WorldData wldData; + + // create a tempLocation + Types::Location_t tempLocation; + + // make a bool for external episodes + bool externalEpisode = false; + + // check to see if the wld file is valid, otherwise don't load the entire episode if booting + if(fileExists(fullPathWS)) + { + std::wstring nonAnsiCharsEpisode = GetNonANSICharsFromWStr(fullPathWS); + if(!nonAnsiCharsEpisode.empty()) + { + std::wstring path = L"The episode path has characters which are not compatible with the system default Windows ANSI code page. This is not currently supported. Please rename or move your episode folder.\n\nUnsupported characters: " + nonAnsiCharsEpisode + L"\n\nPath:\n" + fullPthNoWorldFileWS; + MessageBoxW(0, path.c_str(), L"SMBX does not support episode path", MB_ICONERROR); + _exit(1); + } + + std::wstring nonAnsiCharsFullPath = GetNonANSICharsFromWStr(fullPathWS); + if(!nonAnsiCharsFullPath.empty()) + { + std::wstring path = L"The world map filename has characters which are not compatible with the system default Windows ANSI code page. This is not currently supported. Please rename your world map file.\n\nUnsupported characters: " + nonAnsiCharsFullPath + L"\n\nPath:\n" + fullPathWS; + MessageBoxW(0, path.c_str(), L"SMBX could not load world map", MB_ICONERROR); + _exit(1); + } + + if(!FileFormats::OpenWorldFileHeader(fullPathS, wldData) || !wldData.meta.ReadFileValid) + { + std::wstring path = L"The world map file header cannot be parsed.\n\nPath:\n" + fullPathWS; + MessageBoxW(0, path.c_str(), L"SMBX could not load world map", MB_ICONERROR); + _exit(1); + } + + if(wldData.meta.RecentFormat != WorldData::SMBX64) + { + std::wstring path = L"The world map file is in the wrong format. It must be saved in SMBX64 format.\n\nPath:\n" + fullPathWS; + MessageBoxW(0, path.c_str(), L"SMBX could not load world map", MB_ICONERROR); + _exit(1); + } + } + else + { + std::wstring path = L"SMBX could not find the world map file \"" + fullPathWS + L"\""; + MessageBoxW(0, path.c_str(), L"SMBX could not load world map", MB_ICONERROR); + _exit(1); + } + + // when the episode has loaded successfully after boot, we'll need to do some extra things in order for this to work + if(gEpisodeLoadedOnBoot) + { + // exit lua + gLunaLua.exitContext(); + + // get rid of any additional custom graphics loaded + gCachedFileMetadata.purge(); + } + + // make a worldName string and VB6StrPtr + std::string worldNameS = ""; + VB6StrPtr worldNameVB6 = worldNameS; + + // make an idx int variable + int externalEpisodeIdx = 0; + + // build the episode list + SMBXWorldFileBase::PopulateEpisodeList(); + + // check to see if the episode is external. if so, we'll need to change some things. + if(checkIfWorldIsInWorldPath(fullPathS)) + { + worldNameS = findNameFromEpisodeWorldPath(wldPathS); + } + else + { + // toggle this on + externalEpisode = true; + + // make the episode name the episode's actual name + worldNameS = wldData.EpisodeTitle; + worldNameVB6 = worldNameS; + + // this code, from here, sets the new episode + externalEpisodeIdx = gEpisodeMain.WriteEpisodeEntry(worldNameVB6, fullPthNoWorldFileWithEndSlashVB6, fullWorldFileNoPthVB6, wldData, false); + } + + // reset cheat status + Vars::Cheater = false; + + // reset checkpoints + Vars::Checkpoint = ""; + + // show loadscreen + LunaLoadScreenStart(); + + // setup SFXs + Functions::InitSound(); + + // clear gamedata + LunaLuaSetGameData(0, 0); + + // specify the menu level + if(!externalEpisode) + { + if(Vars::NumSelectWorld < 100) + { + // this NEEDS to be set, otherwise the engine will just crash loading the episode + Vars::selWorld = findEpisodeIDFromWorldFileAndPath(fullPathS); + } + else + { + // make a new variable + int additionalEpIdx = 0; + + // overwrite an episode entry at idx 1 + additionalEpIdx = gEpisodeMain.WriteEpisodeEntry(worldNameVB6, fullPthNoWorldFileWithEndSlashVB6, fullWorldFileNoPthVB6, wldData, false); + + // set the world selection to 1 (This needs to be set, or the engine will crash) + Vars::selWorld = additionalEpIdx; + } + } + else if(externalEpisode) + { + Vars::selWorld = externalEpisodeIdx; + } + + //--BEGIN MAIN RECODE-- + + // implement player count + Vars::numPlayers = playerCount; //--numPlayers = MenuMode / 10 (line 4947)-- + + // make variables for important players + auto& p1 = Vars::Player[1]; + + // apply templates (SavedChar) + SMBX13::Types::Player_t blankPlayer; + + for (int i = 1; i <= Types::numCharacters; i++) //--For A = 1 To numCharacters (line 4948)-- + { + Vars::SavedChar[i] = blankPlayer; //--SavedChar(A) = blankPlayer (line 4949)-- + + Vars::SavedChar[i].Character = i; //--SavedChar(A).Character = A (line 4950)-- + Vars::SavedChar[i].State = 1; //--SavedChar(A).State = 1 (line 4951)-- + } + + // use the character variables that were specified + for (int i = 1; i <= Vars::numPlayers; i++) + { + auto& p = Vars::Player[i]; + + // implement missing player values before loading the save file + p.State = 1; //--Player(1/2).State (line 4953/4964)-- + p.Mount = 0; //--Player(1/2).Mount (line 4954/4965)-- + p.Character = 1; //--Player(1/2).Character = 1 (line 4955/4966)-- + p.HeldBonus = 0; //--Player(1/2).HeldBonus = 0 (line 4956/4967)-- + p.CanFly = 0; //--Player(1/2).CanFly = False (line 4957/4968)-- + p.CanFly2 = 0; //--Player(1/2).CanFly2 = False (line 4958/4969)-- + p.TailCount = 0; //--Player(1/2).TailCount = 0 (line 4959/4970)-- + p.YoshiBlue = 0; //--Player(1/2).YoshiBlue = False (line 4960/4971)-- + p.YoshiRed = 0; //--Player(1/2).YoshiRed = False (line 4961/4972)-- + p.YoshiYellow = 0; //--Player(1/2).YoshiYellow = False (line 4962/4973)-- + p.Hearts = 0; //--Player(1/2).Hearts = 0 (line 4963/4974)-- + } + + // implement the 1st player's character (lines 4975-4978) + if(Vars::numPlayers >= 1) + { + // checks to make sure that the character can be selected or not + if(firstCharacter >= static_cast(1) && firstCharacter <= static_cast(Types::numCharacters)) + { + p1.Character = static_cast(firstCharacter); + } + else + { + p1.Character = 1; + } + } + + // implement the 2nd player's character (lines 4979-4982) + if(Vars::numPlayers >= 2) + { + for (int i = 2; i <= Vars::numPlayers; i++) + { + // get any player above 2 + auto& p2 = Vars::Player[i]; + + // checks to make sure that the character can be selected or not + if(secondCharacter >= static_cast(1) && secondCharacter <= static_cast(Types::numCharacters)) + { + p2.Character = static_cast(secondCharacter); + } + else + { + p2.Character = 2; + } + } + } + + // we'll probably get more than 3 players loading on boot if specified on the command prompt, so this needs to exist (index starts at 2 on the for loop to simulate supermario# cheats) + if(Vars::numPlayers >= 3) + { + for (int i = 2; i <= Vars::numPlayers; i++) + { + auto& p = Vars::Player[i]; + p.Character = Vars::Player[1].Character; + } + } + + if(gEpisodeLoadedOnBoot) // do this too if an episode is already loaded + { + // restore characters if booted already + for (int i = 1; i <= Vars::numPlayers; i++) + { + auto p = Vars::Player[i]; + + // restore this player's character + p.Character = static_cast(gPlayerStoredCharacters[min(i, 4)-1]); + } + } + + // if we have any blocked characters, don't use them and instead specify whatever is not blocked (Not compatible with X2 characters, but they're a mess in basegame so oh well) + for (int i = 1; i <= Vars::numPlayers; i++) + { + checkBlockedCharacterFromWorldAndReplaceCharacterIfSo(i); + } + + // replicating code from 1.3 cause why not + Vars::MenuCursor = saveSlot - 1; + + // implement missing values before loading the save file + Vars::selSave = Vars::MenuCursor + 1; //--selSave = MenuCursor + 1 (line 4983)-- + Vars::numStars = 0; //--numStars = 0 (4984)-- + Vars::Coins = 0; //--Coins = 0 (line 4985)-- + Vars::Score = 0; //--Score = 0 (line 4986)-- + Vars::Lives = 3; //--Lives = 3 (line 4987)-- + + // set that we're on map + Vars::LevelSelect = true; //--LevelSelect = True (line 4988)-- + Vars::GameMenu = false; //--GameMenu = False (line 4989)-- + + /* + skipping these cause lunadll handles this stuff instead + + -- + BitBlt myBackBuffer, 0, 0, ScreenW, ScreenH, 0, 0, 0, vbWhiteness (line 4990) + BitBlt frmMain.hdc, 0, 0, frmMain.ScaleWidth, frmMain.ScaleHeight, 0, 0, 0, vbWhiteness (line 4991) + -- + */ + + // stop music + Functions::StopMusic(); //--StopMusic (line 4992)-- + + /* + skipping these cause lunadll handles this stuff instead + + -- + DoEvents (line 4993) + Sleep 500 (line 4994) + -- + */ + // load the world + Functions::OpenWorld(fullPathVB6); //--OpenWorld SelectWorld(selWorld).WorldPath & SelectWorld(selWorld).WorldFile (line 4995)-- + + // load the save file data + if (gEpisodeMain.FindSaves(fullPthNoWorldFileWithEndSlashS, Vars::selSave) >= 0) //--If SaveSlot(selSave) >= 0 Then (line 4996)-- + { + // blank out intro filename if the episode already has a save file and the intro was already played + if(!Vars::NoMap) //--If NoMap = False Then StartLevel = "" (line 4997)-- + { + Vars::StartLevel = ""; + } + + Functions::LoadGame(); //--LoadGame (line 4998)-- + } //--End If (line 4999)-- + + // get if the illparkwhereiwant cheat is active + if(Vars::WorldUnlock) //--If WorldUnlock = True Then (line 5000)-- + { + // get all paths + for (int i = 1; i <= Vars::numWorldPaths; i++) //--For A = 1 To numWorldPaths (line 5001)-- + { + tempLocation = Vars::WorldPath[i].Location; //--tempLocation = WorldPath(A).Location (line 5002)-- + + //--With tempLocation (line 5003)-- + + tempLocation.X = tempLocation.X + 4; //--.X = .X + 4 (line 5004)-- + tempLocation.Y = tempLocation.Y + 4; //--.Y = .Y + 4 (line 5005)-- + tempLocation.Width = tempLocation.Width - 8; //--.Width = .Width - 8 (line 5006)-- + tempLocation.Height = tempLocation.Height - 8; //--.Height = .Height - 8 (line 5007)-- + + //--End With (line 5008)-- + + // set to active + Vars::WorldPath[i].Active = true; //--WorldPath(A).Active = True (line 5009)-- + + // now get sceneries + for (int j = 1; j <= Vars::numScenes; j++) //--For B = 1 To numScenes (line 5010)-- + { + // check the collision of paths and sceneries + if(gEpisodeMain.CheckCollision(tempLocation, Vars::Scene[j].Location)) //--If CheckCollision(tempLocation, Scene(B).Location) Then Scene(B).Active = False (line 5011)-- + { + // make any scenery if collided invisible if true + Vars::Scene[j].Active = false; + } + } //--Next B (line 5012)-- + } //--Next A (line 5013)-- + + // now get world levels + for (int i = 1; i <= Vars::numWorldLevels; i++) //--For A = 1 To numWorldLevels (line 5014)-- + { + // make them visible + Vars::WorldLevel[i].Visible = true; //--WorldLevel(A).Active = True (line 5015)-- + + } //--Next A (line 5016)-- + + } //--End If (line 5017)-- + + // init SetupPlayers + Functions::SetupPlayers(); //--SetupPlayers (line 5018)-- + + // load the autoboot level if there's no save file, or the hub level if set + if((Vars::StartLevel != blankString && !saveFileExists()) || (Vars::NoMap)) //--If StartLevel <> "" Then-- (line 5019) + { + // make the strings, wstrings, and visual basic 6 string ptr's for the world intro filename + std::string fullPathAndAutobootLvlS = fullPthNoWorldFileWithEndSlashS + (std::string)Vars::StartLevel; + std::wstring fullPathAndAutobootLvlWS = Str2WStr(fullPathAndAutobootLvlS); + VB6StrPtr fullPathAndAutobootLvlVB6 = fullPathAndAutobootLvlS; + + // check to see if the autoboot level exists + if(fileExists(fullPathAndAutobootLvlS)) + { + // load the autoboot level from the episode if we're starting it for the first time, or the hub level if it's a hub-styled episode + + //--PlaySound 28 (line 5020)-- + + //--SoundPause(26) = 200 (line 5021)-- + + Vars::LevelSelect = false; //--LevelSelect = False (line 5022)-- + + //--(line 5023) [left blank]-- + + //--GameThing (line 5024)-- + + Functions::ClearLevel(); //--ClearLevel (line 5025)-- + + //--(line 5026) [left blank]-- + + //--Sleep 1000 (line 5027)-- + + Functions::OpenLevel(fullPathAndAutobootLvlVB6); //--OpenLevel SelectWorld(selWorld).WorldPath & StartLevel (line 5028) + + } //--End If (line 5029)-- + + //--Exit Sub (line 5030)-- + + // if it doesn't exist and there's no hub, error and boot the map instead after clicking "OK" + else if(!fileExists(fullPathAndAutobootLvlS) && !Vars::NoMap) + { + std::wstring path = L"The level autoboot file can not be loaded. Does it even exist?\n\nAutoboot level:\n" + fullPathWS; + MessageBoxW(0, path.c_str(), L"SMBX could not load the autoboot level", MB_ICONERROR); + + // boot the map + Vars::LevelSelect = true; + } + // else if it doesn't exist and there IS a hub, error and exit instead after clicking "OK" + else if(!fileExists(fullPathAndAutobootLvlS) && Vars::NoMap) + { + std::wstring path = L"The level hub file can not be loaded. Does it even exist?\n\nAutoboot level:\n" + fullPathWS + L"\n\nBecause there is no hub level, the game will now close after clicking OK. Please put in a valid hub level file in the episode before loading it."; + MessageBoxW(0, path.c_str(), L"SMBX could not load the hub level", MB_ICONERROR); + _exit(1); + } + } + + // make sure that lunadll knows the game loaded on boot, so that loadEpisode can know + if(!gEpisodeLoadedOnBoot) + { + gEpisodeLoadedOnBoot = true; + } + + // hide loadscreen + LunaLoadScreenKill(); + + //--End If (line 5031)-- + + //--END MAIN RECODE-- +} + +int EpisodeMain::FindSaves(std::string worldPathS, int saveSlot) +{ + using namespace SMBX13; + + // FileFormats SaveData, used for getting the save slot data + GamesaveData saveData; + + //--BEGIN MAIN RECODE-- + if(saveSlot >= 0) // is the save slot greater than or equal to 0? + { + /* + -- + **string references** + + - world path to the .sav file + - world path to the .savx file + -- + */ + + std::string saveFileS = worldPathS + "save" + std::to_string(saveSlot) + ".sav"; //--If Dir(SelectWorld(selWorld).WorldPath & "save" & A & ".sav") <> "" Then (line 7700)-- + std::string saveFileXtraS = worldPathS + "save" + std::to_string(saveSlot) + ".savx"; + + /* + -- + **wstring references** + + - world path to the .sav file + - world path to the .savx file + -- + */ + + std::wstring saveFileWS = Str2WStr(saveFileS); + std::wstring saveFileXtraWS = Str2WStr(saveFileXtraS); + + // if the .sav file exists, and FileFormats successfully reads the sav file as a SMBX 1.3 save file, continue + if(fileExists(saveFileS) && FileFormats::ReadSMBX64SavFileF(saveFileS, saveData)) //--Open SelectWorld(selWorld).WorldPath & "save" & A & ".sav" For Input As #1 (line 7701)-- + { + // so basically the original FindSaves loops all over the save files reading things in a way that is only supported on VB6, but since we've got FileFormats we can breeze through this without relying on old methods of reading save files! + int curActive = 0; + int maxActive = 0; + + // the game beat flag + maxActive++; + if(saveData.gameCompleted) + { + curActive++; + } + + // how much paths are open + maxActive += (int)saveData.visiblePaths.size(); + for(auto &p : saveData.visiblePaths) + { + if(p.second) + { + curActive++; + } + } + + // how much levels are opened + maxActive += (int)saveData.visibleLevels.size(); + for(auto &p : saveData.visibleLevels) + { + if(p.second) + { + curActive++; + } + } + + // how many stars are collected + maxActive += (int(saveData.totalStars) * 4); + curActive += (int(saveData.gottenStars.size()) * 4); + + // calculate the progress number + if(maxActive > 0) + { + return int((float(curActive) / float(maxActive)) * 100); + } + else + { + return 100; + } + } + // we don't support savx files yet, so this part will be commented out + /*else if(fileExists(saveFileXtraS) && FileFormats::ReadExtendedSaveFileF(saveFileXtraS, saveData)) + { + + }*/ + else + { + // -1 means the data wasn't found, which is new in the case of detecting if the file wasn't found + return -1; + } + } + else + { + // -2 means the save data isn't valid, which is new for this case + return -2; + } + + //--End If (line 7763)-- + + //--END MAIN RECODE-- +} + +int EpisodeMain::WriteEpisodeEntry(VB6StrPtr worldNameVB6, VB6StrPtr worldPathVB6, VB6StrPtr worldFileVB6, WorldData wldData, bool isNewEpisode) +{ + using namespace SMBX13; + + int newIdx = 0; + + // if there's at least/more than 100 episodes, use episode idx 1 and overwrite the entry + if(Vars::NumSelectWorld >= 100) + { + if(isNewEpisode) + { + newIdx = 1; + } + else + { + newIdx = 1; + } + + // also set the world count to 100, since the count was bigger than what is expected + Vars::NumSelectWorld = 100; + } + // if there's less than 100 episodes, add a new episode into the list + else if(Vars::NumSelectWorld < 100) + { + if(isNewEpisode) + { + // increase the episode count + Vars::NumSelectWorld++; + + // set the new idx + newIdx = Vars::NumSelectWorld; + } + else + { + newIdx = 1; + } + } + + // set the world name, path, and world filename + auto& item = Vars::SelectWorld[newIdx]; + + item.WorldName = worldNameVB6; + item.WorldPath = worldPathVB6; + item.WorldFile = worldFileVB6; + + // set characters that are blocked according to the wld file itself + for (size_t i = 1; i <= 5; i++) + { + if (i < wldData.nocharacter.size()) + { + item.blockChar[i] = wldData.nocharacter[i - 1]; + } + else + { + item.blockChar[i] = false; + } + } + + return newIdx; +} + +bool EpisodeMain::CheckCollision(SMBX13::Types::Location_t momentumA, SMBX13::Types::Location_t momentumB) +{ + return ((momentumA.Y + momentumA.Height >= momentumB.Y) && + (momentumA.Y <= momentumB.Y + momentumB.Height) && + (momentumA.X <= momentumB.X + momentumB.Width) && + (momentumA.X + momentumA.Width >= momentumB.X)); +} diff --git a/LunaDll/SMBXInternal/Reconstructed/EpisodeMain.h b/LunaDll/SMBXInternal/Reconstructed/EpisodeMain.h new file mode 100644 index 00000000..e5dcd544 --- /dev/null +++ b/LunaDll/SMBXInternal/Reconstructed/EpisodeMain.h @@ -0,0 +1,28 @@ +// EpisodeLoader.h +#ifndef EpisodeLoader_hhh +#define EpisodeLoader_hhh + +#include +#include + +#include "../Menu.h" +#include "../../libs/PGE_File_Formats/file_formats.h" + +#include "../Functions.h" +#include "../Types.h" +#include "../Variables.h" + +class EpisodeMain { + public: + EpisodeMain(); + ~EpisodeMain(); + + void LaunchEpisode(std::wstring wldPathWS, int saveSlot, int playerCount, Characters firstCharacter, Characters secondCharacter); + int FindSaves(std::string worldPathS, int saveSlot); + int WriteEpisodeEntry(VB6StrPtr worldNameVB6, VB6StrPtr worldPathVB6, VB6StrPtr worldFileVB6, WorldData wldData, bool isNewEpisode); + bool CheckCollision(SMBX13::Types::Location_t momentumA, SMBX13::Types::Location_t momentumB); +}; + +extern EpisodeMain gEpisodeMain; + +#endif