diff --git a/.gitignore b/.gitignore index 605b9d44b..db00ed717 100644 --- a/.gitignore +++ b/.gitignore @@ -552,3 +552,4 @@ include/IsaacRepentance.h libzhl/IsaacRepentance.cpp updater/updater.rc updater_rsrc +updater_rsrc diff --git a/libzhl/functions/ExtraTypes b/libzhl/functions/ExtraTypes index 64309cc9d..e343e9a43 100644 --- a/libzhl/functions/ExtraTypes +++ b/libzhl/functions/ExtraTypes @@ -60,6 +60,11 @@ TypeInfo vector_MusicEntry { Align 4; }; +TypeInfo vector_FXLayers_FX { + Size 12; + Align 4; +}; + TypeInfo vector_Curse { Size 12; Align 4; diff --git a/libzhl/functions/FXLayers.zhl b/libzhl/functions/FXLayers.zhl new file mode 100644 index 000000000..c1f11964d --- /dev/null +++ b/libzhl/functions/FXLayers.zhl @@ -0,0 +1,15 @@ +"538bdc83ec0883e4f883c404558b6b??896c24??8bec6aff68????????64a1????????50515351b87c020100e8????????a1????????33c58945??5657508d45??64a3????????8965??8bf1": +__thiscall void FXLayers::Init(char * fileName, int levelStage, int stageType); + +"558bec53568bf18bda": +static cleanup bool FXLayers::check_fxlayer_match(int stage, int altStages, int compLevelStage, int compStageType); + +struct FXLayers depends (Vector, FXParams) { + Vector _averagePlayerPos : 0x0; + bool _loaded : 0x8; + int _levelStage : 0x0c; + int _stageType : 0x10; + int _backdropType : 0x14; + vector_FXLayers_FX _fx : 0x18; + FXParams _fxParams : 0x4f8; +} : 0x584; diff --git a/libzhl/functions/FXLayers_FX.zhl b/libzhl/functions/FXLayers_FX.zhl new file mode 100644 index 000000000..9786aaf6c --- /dev/null +++ b/libzhl/functions/FXLayers_FX.zhl @@ -0,0 +1,15 @@ +struct FXLayers_FX depends (Vector) { + unsigned int _id : 0x0; + KAGE_Graphics_ImagePNG* _image : 0x4; + // smartpointer 0x8 + Vector _min : 0x1c; + Vector _max : 0x24; + unsigned int _layerType : 0x2c; + int _stage : 0x30; + int _altStages : 0x34; + bool _onlyDefaultBackdrop : 0x38; + float _parallax : 0x3c; + int _backdrop : 0x40; + int _blendMode : 0x44; + bool _useBackgroundShader : 0x48; +} : 0x4c; diff --git a/libzhl/functions/Game.zhl b/libzhl/functions/Game.zhl index aa27d2f58..bd133a590 100644 --- a/libzhl/functions/Game.zhl +++ b/libzhl/functions/Game.zhl @@ -141,6 +141,9 @@ __thiscall bool Game::GetLevelStateFlag(uint32_t LevelStateFlag); "568bf18b0d????????8b81????????83f80274??83f80374??8b168d42??83f80577??8b89????????33c081e1000001000bc175??8b46??83f80474??83f80574": __thiscall bool Game::LevelHasPhotoDoor(); +"8b0148": +__thiscall bool Game::IsAscent(); + "e8????????c745fcffffffffa3(????????)e8": reference Game *g_Game; @@ -283,6 +286,7 @@ struct Game depends (Level, RoomDescriptor, RoomConfig, BossPool, PlayerManager, vector_ErasedEntities _erasedEntities : 0x283c14; bool _triggerWindowResize : 0x1c3154; unsigned int _gameStateFlags : 0x1c316c; + unsigned int _gameStateFlagsH : 0x1c3170; int _donationModGreed : 0x1c318c; int _donationModAngel : 0x1c3190; float _lightningIntensity : 0x283c00; diff --git a/libzhl/functions/Global.zhl b/libzhl/functions/Global.zhl index 68abb7b47..2fbd25b6a 100644 --- a/libzhl/functions/Global.zhl +++ b/libzhl/functions/Global.zhl @@ -50,6 +50,7 @@ struct IntPair { // When adding typedefs, remember to define them in ExtraTypes! typedef std::vector vector_MusicEntry; +typedef std::vector vector_FXLayers_FX; typedef std::vector vector_SoundEffect; typedef std::vector vector_Curse; typedef std::deque deque_Point; diff --git a/libzhl/functions/Isaac.zhl b/libzhl/functions/Isaac.zhl index 3ea872a02..fba38e5ad 100644 --- a/libzhl/functions/Isaac.zhl +++ b/libzhl/functions/Isaac.zhl @@ -55,6 +55,9 @@ static __x86_64_output Vector Isaac::GetCollectibleSpawnPosition(Vector* target) "56578bfa8bf183ffff": static __x86_64_output Vector Isaac::GetAxisAlignedUnitVectorFromDir(int eDir); +"558bec0f1002": +__fastcall void IsaacRepentance::SwapANM2(ANM2 * left, ANM2 * right); + struct Isaac { {{ LIBZHL_API static bool IsInGame(); diff --git a/libzhl/functions/KAGE_Graphics_Image.zhl b/libzhl/functions/KAGE_Graphics_Image.zhl index 256e4cac1..38f9b3948 100644 --- a/libzhl/functions/KAGE_Graphics_Image.zhl +++ b/libzhl/functions/KAGE_Graphics_Image.zhl @@ -4,6 +4,7 @@ struct KAGE_Graphics_ImageBase_VertexAttributeDescriptor { } : 0x8; struct KAGE_Graphics_ImageBase depends (SourceQuad, DestinationQuad, KAGE_Graphics_Color) { + char* _filepath : 0x3c; __vtable { skip; // IsLoaded skip; // IsProcedural @@ -49,7 +50,7 @@ struct KAGE_Graphics_ImageBase depends (SourceQuad, DestinationQuad, KAGE_Graphi skip; // apply_image skip; // SetTexelData }; -} : 0; +} : 0x78; struct KAGE_Graphics_ImagePNG : public KAGE_Graphics_ImageBase { diff --git a/libzhl/functions/Level.zhl b/libzhl/functions/Level.zhl index 8644604a4..5dc1a2b52 100644 --- a/libzhl/functions/Level.zhl +++ b/libzhl/functions/Level.zhl @@ -49,6 +49,9 @@ __thiscall bool Level::IsAltPath(); "558bec83e4f883ec345356576aff": __thiscall void Level::InitializeDevilAngelRoom(bool ForceAngel, bool ForceDevil); +"558beca1????????83ec0c83b8????????2c": +__thiscall unsigned int Level::GetLocalStageType(int idx); + "558bec6aff68????????64a1????????5083ec24535657a1????????33c5508d45??64a3????????8bd9f30f7e05": __thiscall int Level::GetRandomRoomIndex(bool IAmErrorRoom, unsigned int Seed); diff --git a/libzhl/functions/RoomConfig.zhl b/libzhl/functions/RoomConfig.zhl index 10fd45898..eabc00adf 100644 --- a/libzhl/functions/RoomConfig.zhl +++ b/libzhl/functions/RoomConfig.zhl @@ -2,13 +2,13 @@ __thiscall void RoomConfig::LoadCurses(char* xmlpath,bool ismod); "558bec8b45??83f8ff75??a1": -static cleanup unsigned int RoomConfig::GetStageID(unsigned int LevelStage, unsigned int StageType, unsigned int Mode); +static cleanup unsigned int RoomConfig::GetStageID(unsigned int LevelStage, unsigned int StageType, int Mode); "538bdc83ec0883e4f883c404558b6b??896c24??8bec6aff68????????64a1????????50515351b894010100": __thiscall void RoomConfig::LoadStages(char *xmlpath); "538bdc83ec0883e4f883c404558b6b??896c24??8bec6aff68????????64a1????????505383ec6c": -__thiscall void RoomConfig::LoadStageBinary(unsigned int Stage, unsigned int Mode); +__thiscall bool RoomConfig::LoadStageBinary(unsigned int Stage, unsigned int Mode); // RoomConfigHolder diff --git a/libzhl/functions/RoomConfigStage.zhl b/libzhl/functions/RoomConfigStage.zhl index b21eebd68..9f4925388 100644 --- a/libzhl/functions/RoomConfigStage.zhl +++ b/libzhl/functions/RoomConfigStage.zhl @@ -16,4 +16,4 @@ struct RoomConfig_Stage depends (RoomSet) { RoomSet _rooms[2] : 0x64; int _musicId : 0xbc; int _backdrop : 0xc0; -} : 0xc4; \ No newline at end of file +} : 0xc4; diff --git a/libzhl/functions/RoomSet.zhl b/libzhl/functions/RoomSet.zhl index 5da8930c1..90e2863e7 100644 --- a/libzhl/functions/RoomSet.zhl +++ b/libzhl/functions/RoomSet.zhl @@ -1,10 +1,20 @@ +"558bec6aff68????????64a1????????5083ec085657a1????????33c5508d45??64a3????????8bf9897d??c70700000000c747??00000000c747??0f000000c60700c745??00000000": +__thiscall RoomSet* RoomSet::constructor(); + +"56578bf98b47??8d77??8bceff70??56e8????????6a18ff36e8????????83c408": +__thiscall void RoomSet::destructor(); + "558bec6aff68????????64a1????????5083ec10535657a1????????33c5508d45??64a3????????8bd9895d??8b43??85c0": __thiscall void RoomSet::unload(); -struct RoomSet -{ +struct RoomSet { +{{ + RoomSet() { constructor(); } + LIBZHL_API ~RoomSet() { destructor(); } +}} std_string _filepath : 0x0; RoomConfig_Room* _configs : 0x18; unsigned int _count : 0x1c; + std_set_int _ids : 0x20; bool _loaded : 0x28; } : 0x2c; diff --git a/repentogon/LuaInterfaces/LuaLevel.cpp b/repentogon/LuaInterfaces/LuaLevel.cpp index 26ebbe622..8e5e21635 100644 --- a/repentogon/LuaInterfaces/LuaLevel.cpp +++ b/repentogon/LuaInterfaces/LuaLevel.cpp @@ -102,6 +102,12 @@ LUA_FUNCTION(Lua_LevelIsAltPath) { return 1; } +LUA_FUNCTION(Lua_GetStageId) { + Game* level = lua::GetUserdata(L, 1, lua::Metatables::LEVEL, "Level"); + lua_pushinteger(L, level->GetStageID(false)); + return 1; +} + /* HOOK_METHOD(Level, IsAltPath, () -> bool) { bool ret = false; @@ -312,8 +318,8 @@ HOOK_METHOD(LuaEngine, RegisterClasses, () -> void) { { "GetDimension", Lua_GetDimension}, { "GetForceSpecialQuest", Lua_GetForceSpecialQuest }, { "SetForceSpecialQuest", Lua_SetForceSpecialQuest }, + { "GetStageId", Lua_GetStageId }, { "GetMyosotisPickups", Lua_GetMyosotisPickups }, - { "SetGreedWavesClearedWithoutRedHeartDamage", lua_LevelSetGreedWavesClearedWithoutRedHeartDamage }, { "GetGreedWavesClearedWithoutRedHeartDamage", lua_LevelGetGreedWavesClearedWithoutRedHeartDamage }, diff --git a/repentogon/LuaInterfaces/LuaSprite.cpp b/repentogon/LuaInterfaces/LuaSprite.cpp index d8821d377..2d4d3822d 100644 --- a/repentogon/LuaInterfaces/LuaSprite.cpp +++ b/repentogon/LuaInterfaces/LuaSprite.cpp @@ -284,7 +284,6 @@ LUA_FUNCTION(Lua_SpriteHasCustomChampionShader) return 1; } - // LayerState from here on out LUA_FUNCTION(Lua_LayerStateSetCustomShader) diff --git a/repentogon/LuaInterfaces/Room/LuaRoom.cpp b/repentogon/LuaInterfaces/Room/LuaRoom.cpp index 85c2fe99e..9180e5c91 100644 --- a/repentogon/LuaInterfaces/Room/LuaRoom.cpp +++ b/repentogon/LuaInterfaces/Room/LuaRoom.cpp @@ -2,8 +2,11 @@ #include "LuaCore.h" #include "HookSystem.h" #include "Room.h" +#include "Log.h" +#include "../../Patches/XMLData.h" #include "../../Patches/CustomItemPools.h" +#include "../../Patches/Stages/StageManager.h" RoomASM roomASM; extern uint32_t hookedbackdroptype; @@ -379,15 +382,15 @@ LUA_FUNCTION(Lua_RoomGetNumRainSpawners) { LUA_FUNCTION(Lua_RoomGetBackdropTypeHui) { //this is a bad way to replace room.GetBackdropType, I think Room* room = lua::GetUserdata(L, 1, lua::Metatables::ROOM, lua::metatables::RoomMT); - if (hookedbackdroptype != 0) { - lua_pushinteger(L, hookedbackdroptype); - return 1; + if (XMLStuff.BackdropData->backdropState.first) { + lua_pushinteger(L, XMLStuff.BackdropData->backdropState.second); } else { Backdrop* bg = room->GetBackdrop(); lua_pushinteger(L, bg->backdropId); - return 1; } + + return 1; } LUA_FUNCTION(Lua_RoomSaveState) { @@ -483,10 +486,24 @@ HOOK_METHOD(Room, Init, (int param_1, RoomDescriptor * desc) -> void) { roomASM.WaterDisabled = false; roomASM.ItemPool = POOL_NULL; super(param_1, desc); - //printf("WaterDisabled is %s, stage is %d\n", roomASM.WaterDisabled ? "TRUE" : "FALSE", g_Game->_stage); - if (g_Game->_stage == 12 && !roomASM.WaterDisabled && (this->_descriptor->Data->StageId == 27 || this->_descriptor->Data->StageId == 28)) { - this->_waterAmount = 1.0f; - //printf("setting water\n"); + //ZHL::Log("WaterDisabled is %s, stage is %d\n", roomASM.WaterDisabled ? "TRUE" : "FALSE", g_Game->_stage); + if (!roomASM.WaterDisabled) { + unsigned int stbType = this->_descriptor->Data->StageId; + if (g_Game->_stage == 12 && (stbType == 27 || stbType == 28)) { + this->_waterAmount = 1.0f; + //ZHL::Log("setting water\n"); + } + else { + StageManager& stageManager = StageManager::GetInstance(); + stbType = RoomConfig::GetStageID(g_Game->_stage, g_Game->_stageType, g_Game->IsGreedMode()); + if (stageManager.IsStageOverriden(stbType)) { + XMLAttributes xmlData = XMLStuff.StageData->GetNodeById(stageManager.stageForConfigId[stbType]); + if (tobool(xmlData["haswater"])) { + this->_waterAmount = 1.0f; + //ZHL::Log("setting water\n"); + } + } + } } } diff --git a/repentogon/LuaInterfaces/Room/LuaRoomConfig.cpp b/repentogon/LuaInterfaces/Room/LuaRoomConfig.cpp index 51ee6ccd3..672ef16e4 100644 --- a/repentogon/LuaInterfaces/Room/LuaRoomConfig.cpp +++ b/repentogon/LuaInterfaces/Room/LuaRoomConfig.cpp @@ -2,26 +2,50 @@ #include "LuaCore.h" #include "HookSystem.h" -/*LUA_FUNCTION(Lua_GameGetRoomConfig) { - Game* game = lua::GetUserdata(L, 1, lua::Metatables::GAME, "Game"); - RoomConfig** ud = (RoomConfig**)lua_newuserdata(L, sizeof(RoomConfig*)); - *ud = game->GetRoomConfig(); - luaL_setmetatable(L, lua::metatables::RoomConfigMT); +#include "../../Patches/Stages/StageManager.h" + +LUA_FUNCTION(Lua_RoomConfig_GetStage) { + StageManager& stageManager = StageManager::GetInstance(); + RoomConfig* roomConfig = g_Game->GetRoomConfig(); + int stage = (int)luaL_checkinteger(L, 1); + + if (stage < 0 || stage > 36) { + return luaL_error(L, "StageID must be between 0 and 36 (both inclusive), got %d\n", stage); + } + + if (stage == stageManager.BUFFER_STAGEID) { + return luaL_error(L, "Invalid StageID %d\n", stage); + } + + RoomConfig_Stage* configStage = &roomConfig->_stages[stage]; + //printf("%p, %p, %d\n", roomConfig, configStage, configStage->_musicId); + + RoomConfig_Stage** ud = (RoomConfig_Stage**)lua_newuserdata(L, sizeof(RoomConfig_Stage*)); + *ud = configStage; + luaL_setmetatable(L, lua::metatables::RoomConfigStageMT); return 1; } -*/ LUA_FUNCTION(Lua_RoomConfig_GetRoomByStageTypeAndVariant) { + StageManager& stageManager = StageManager::GetInstance(); int n = lua_gettop(L); if (n < 3) { return luaL_error(L, "Expected three parameters, got %d\n", n); } RoomConfig* roomConfig = g_Game->GetRoomConfig(); - int stage = (int)luaL_checkinteger(L, 1); + int stage = -1; + RoomSet* set; - if (stage < 0 || stage > 36) { - return luaL_error(L, "StageID must be between 0 and 36 (both inclusive), got %d\n", stage); + if (lua_type(L, 1) == LUA_TUSERDATA) { + set = *lua::GetUserdata(L, 1, lua::metatables::RoomConfigSetMT); + } + else + { + stage = (int)luaL_checkinteger(L, 1); + if (stage < 0 || stage > 36) { + return luaL_error(L, "StageID must be between 0 and 36 (both inclusive), got %d\n", stage); + } } int type = (int)luaL_checkinteger(L, 2); @@ -35,25 +59,52 @@ LUA_FUNCTION(Lua_RoomConfig_GetRoomByStageTypeAndVariant) { mode = -1; } - RoomConfig_Room* config = roomConfig->GetRoomByStageTypeAndVariant(stage, type, variant, mode); - if (!config) { + RoomConfig_Room* config; + if (stage == -1) { + // swap in + //RoomSet oldSet = roomConfig->_stages[stageManager.BUFFER_STAGEID]._rooms[0]; + roomConfig->_stages[stageManager.BUFFER_STAGEID]._rooms[0] = *set; + + // get room + config = roomConfig->GetRoomByStageTypeAndVariant(stageManager.BUFFER_STAGEID, type, variant, mode); + + // swap out + *set = roomConfig->_stages[stageManager.BUFFER_STAGEID]._rooms[0]; + //roomConfig->_stages[stageManager.BUFFER_STAGEID]._rooms[0] = oldSet; + } + else + { + config = roomConfig->GetRoomByStageTypeAndVariant(stage, type, variant, mode); + } + + if (config == NULL) { lua_pushnil(L); } - else { - lua::luabridge::UserdataPtr::push(L, config, lua::GetMetatableKey(lua::Metatables::CONST_ROOM_CONFIG_ROOM)); + else + { + lua::luabridge::UserdataPtr::push(L, config, lua::Metatables::CONST_ROOM_CONFIG_ROOM); } return 1; } LUA_FUNCTION(Lua_RoomConfig_GetRandomRoom) { + StageManager& stageManager = StageManager::GetInstance(); RoomConfig* roomConfig = g_Game->GetRoomConfig(); int seed = (int)luaL_checkinteger(L, 1); bool reduceWeight = lua::luaL_checkboolean(L, 2); + int stage = -1; + RoomSet* set; - int stage = (int)luaL_checkinteger(L, 3); - if (stage < 0 || (stage > 17 && stage < 27) || stage > 36) { - return luaL_error(L, "Invalid stage %d\n", stage); + if (lua_type(L, 3) == LUA_TUSERDATA) { + set = *lua::GetUserdata(L, 3, lua::metatables::RoomConfigSetMT); + } + else + { + stage = (int)luaL_checkinteger(L, 3); + if (stage < 0 || (stage > 17 && stage < 27) || stage > 36) { + return luaL_error(L, "Invalid stage %d\n", stage); + } } int type = (int)luaL_checkinteger(L, 4); @@ -103,26 +154,32 @@ LUA_FUNCTION(Lua_RoomConfig_GetRandomRoom) { return luaL_error(L, "Invalid mode %d\n", mode); } + RoomConfig_Room* config; + if (stage == -1) { + // swap in + //RoomSet oldSet = roomConfig->_stages[stageManager.BUFFER_STAGEID]._rooms[0]; + roomConfig->_stages[stageManager.BUFFER_STAGEID]._rooms[0] = *set; - RoomConfig_Room* config = roomConfig->GetRandomRoom(seed, reduceWeight, stage, type, shape, minVariant, maxVariant, minDifficulty, maxDifficulty, (unsigned int*)&doors, subtype, mode); - lua::luabridge::UserdataPtr::push(L, config, lua::Metatables::CONST_ROOM_CONFIG_ROOM); - return 1; -} - -LUA_FUNCTION(Lua_RoomConfig_GetStage) { - RoomConfig* roomConfig = g_Game->GetRoomConfig(); - int stage = (int)luaL_checkinteger(L, 1); + // get room + config = roomConfig->GetRandomRoom(seed, reduceWeight, stageManager.BUFFER_STAGEID, type, shape, minVariant, maxVariant, minDifficulty, maxDifficulty, (unsigned int*)&doors, subtype, mode); - if (stage < 0 || stage > 36) { - return luaL_error(L, "StageID must be between 0 and 36 (both inclusive), got %d\n", stage); + // swap out + *set = roomConfig->_stages[stageManager.BUFFER_STAGEID]._rooms[0]; + //roomConfig->_stages[BUFFER_STAGEID]._rooms[0] = oldSet; + } + else + { + config = roomConfig->GetRandomRoom(seed, reduceWeight, stage, type, shape, minVariant, maxVariant, minDifficulty, maxDifficulty, (unsigned int*)&doors, subtype, mode); } - RoomConfig_Stage* configStage = &roomConfig->_stages[stage]; - printf("%p, %p, %d\n", roomConfig, configStage, configStage->_musicId); + if (config == NULL) { + lua_pushnil(L); + } + else + { + lua::luabridge::UserdataPtr::push(L, config, lua::Metatables::CONST_ROOM_CONFIG_ROOM); + } - RoomConfig_Stage** ud = (RoomConfig_Stage**)lua_newuserdata(L, sizeof(RoomConfig_Stage*)); - *ud = configStage; - luaL_setmetatable(L, lua::metatables::RoomConfigStageMT); return 1; } diff --git a/repentogon/LuaInterfaces/Room/LuaRoomConfigSet.cpp b/repentogon/LuaInterfaces/Room/LuaRoomConfigSet.cpp index 3e4946700..50f41f3a0 100644 --- a/repentogon/LuaInterfaces/Room/LuaRoomConfigSet.cpp +++ b/repentogon/LuaInterfaces/Room/LuaRoomConfigSet.cpp @@ -25,6 +25,14 @@ LUA_FUNCTION(Lua_RoomConfigSetGetSize) return 1; } +LUA_FUNCTION(Lua_RoomConfigSet__gc) +{ + RoomSet* set = *lua::GetUserdata(L, 1, lua::metatables::RoomConfigSetMT); + set->destructor(); + + return 0; +} + static void RegisterRoomConfigSet(lua_State* L) { luaL_newmetatable(L, lua::metatables::RoomConfigSetMT); lua_pushstring(L, "__index"); @@ -51,6 +59,7 @@ static void RegisterRoomConfigSet(lua_State* L) { luaL_Reg functions[] = { { "Get", Lua_RoomConfigSetGetRoom }, { "__len", Lua_RoomConfigSetGetSize }, + { "__gc", Lua_RoomConfigSet__gc }, { NULL, NULL } }; diff --git a/repentogon/LuaInterfaces/Room/LuaRoomConfigStage.cpp b/repentogon/LuaInterfaces/Room/LuaRoomConfigStage.cpp index 9cf77f09f..f0283fed1 100644 --- a/repentogon/LuaInterfaces/Room/LuaRoomConfigStage.cpp +++ b/repentogon/LuaInterfaces/Room/LuaRoomConfigStage.cpp @@ -54,6 +54,19 @@ LUA_FUNCTION(Lua_RoomConfigStageGetRoomSet) return 1; } +LUA_FUNCTION(Lua_RoomConfigStageSetRoomSet) +{ + RoomConfig_Stage* stage = *lua::GetUserdata(L, 1, lua::metatables::RoomConfigStageMT); + RoomSet* set = *lua::GetUserdata(L, 2, lua::metatables::RoomConfigSetMT); + int mode = (int)luaL_optinteger(L, 3, 0); + if (mode < 0 || mode > 1) { + return luaL_error(L, "Invalid RoomSet mode %d", mode); + } + stage->_rooms[mode] = *set; + + return 0; +} + LUA_FUNCTION(Lua_RoomConfigStageGetID) { RoomConfig_Stage* stage = *lua::GetUserdata(L, 1, lua::metatables::RoomConfigStageMT); @@ -65,8 +78,9 @@ LUA_FUNCTION(Lua_RoomConfigStageGetID) LUA_FUNCTION(Lua_RoomConfigStageGetDisplayName) { RoomConfig_Stage* stage = *lua::GetUserdata(L, 1, lua::metatables::RoomConfigStageMT); - lua_pushstring(L, stage->_displayName.c_str()); - + std::string* name = &stage->_displayName; + lua_pushstring(L, name->empty() ? "" : name->c_str()); + return 1; } @@ -81,7 +95,8 @@ LUA_FUNCTION(Lua_RoomConfigStageSetDisplayName) LUA_FUNCTION(Lua_RoomConfigStageGetPlayerSpot) { RoomConfig_Stage* stage = *lua::GetUserdata(L, 1, lua::metatables::RoomConfigStageMT); - lua_pushstring(L, stage->_playerSpot.c_str()); + std::string* name = &stage->_playerSpot; + lua_pushstring(L, name->empty() ? "" : name->c_str()); return 1; } @@ -97,7 +112,8 @@ LUA_FUNCTION(Lua_RoomConfigStageSetPlayerSpot) LUA_FUNCTION(Lua_RoomConfigStageGetBossSpot) { RoomConfig_Stage* stage = *lua::GetUserdata(L, 1, lua::metatables::RoomConfigStageMT); - lua_pushstring(L, stage->_bossSpot.c_str()); + std::string* name = &stage->_bossSpot; + lua_pushstring(L, name->empty() ? "" : name->c_str()); return 1; } @@ -113,7 +129,8 @@ LUA_FUNCTION(Lua_RoomConfigStageSetBossSpot) LUA_FUNCTION(Lua_RoomConfigStageGetSuffix) { RoomConfig_Stage* stage = *lua::GetUserdata(L, 1, lua::metatables::RoomConfigStageMT); - lua_pushstring(L, stage->_suffix.c_str()); + std::string* name = &stage->_suffix; + lua_pushstring(L, name->empty() ? "" : name->c_str()); return 1; } @@ -129,19 +146,30 @@ LUA_FUNCTION(Lua_RoomConfigStageSetSuffix) LUA_FUNCTION(Lua_RoomConfigStageGetXMLName) { RoomConfig_Stage* stage = *lua::GetUserdata(L, 1, lua::metatables::RoomConfigStageMT); - std::string* string = &stage->_rooms[0]._filepath; - lua_pushstring(L, string->substr(6).c_str()); + int mode = (int)luaL_optinteger(L, 2, 0); + if (mode < 0 || mode > 1) { + return luaL_error(L, "Invalid RoomSet mode %d", mode); + } + std::string* name = &stage->_rooms[mode]._filepath; + lua_pushstring(L, name->empty() ? "" : name->c_str()); + return 1; } LUA_FUNCTION(Lua_RoomConfigStageSetXMLName) { RoomConfig_Stage* stage = *lua::GetUserdata(L, 1, lua::metatables::RoomConfigStageMT); + int mode = (int)luaL_optinteger(L, 2, -1); + if (mode > 1) { + mode = -1; + } std::string name = luaL_checkstring(L, 2); - stage->_rooms[0]._filepath = "rooms/" + name; - stage->_rooms[1]._filepath = "rooms/greed/" + name; + if (mode == -1 || mode == 0) + stage->_rooms[0]._filepath = name; + if (mode == -1 || mode == 1) + stage->_rooms[1]._filepath = name; return 0; } @@ -182,6 +210,7 @@ static void RegisterRoomConfigStage(lua_State* L) { { "GetSuffix", Lua_RoomConfigStageGetSuffix }, { "SetSuffix", Lua_RoomConfigStageSetSuffix }, { "GetRoomSet", Lua_RoomConfigStageGetRoomSet }, + { "SetRoomSet", Lua_RoomConfigStageSetRoomSet }, { "GetXMLName", Lua_RoomConfigStageGetXMLName }, { "SetXMLName", Lua_RoomConfigStageSetXMLName }, { "IsLoaded", Lua_RoomConfigStageGetRoomSetLoaded }, diff --git a/repentogon/Patches/ASMPatches.cpp b/repentogon/Patches/ASMPatches.cpp index a6f1dd3fd..683ad9175 100644 --- a/repentogon/Patches/ASMPatches.cpp +++ b/repentogon/Patches/ASMPatches.cpp @@ -29,6 +29,8 @@ #include "ASMPatcher.hpp" +#include "IsaacRepentance.h" + /* This patch hooks KAGE_LogMessage by hand. LibZHL can't properly hook functions with varargs, and we need varargs to properly get log messages. * So, we'll just do it manually, not a big deal. * We manually call a trampoline function that takes a const char* as an input, and prints it to our ImGui log. @@ -76,6 +78,21 @@ void ASMPatchConsoleRunCommand() { sASMPatcher.FlatPatch(addr, &patch); } +/* Override our destructor of RoomSet. + * + * We need this destructor defined on our side in order to free RoomSets that + * are instanciated on the stack. However, defining it on our side causes its + * contents to be destroyed as well, which leads to corruptions when the + * destructor defined in the game is called. + * + * As a solution, patch our own destructor to immediately jump into the + * destructor defined by the game. This cannot be done through inline assembly + * because the compiler may preserve things on the stack, and not cleaning it + * up would trigger errors down the way. This effectively turns our destructor + * into an immediate trampoline towards the destructor defined in the game. + */ +static void PatchRoomSetDestructor(); + const char* repentogonResources = "resources-repentogon"; void ASMBuildResoucesRepentogon() { @@ -148,6 +165,7 @@ void PerformASMPatches() { ASMPatchBlueWombCurse(); ASMPatchVoidGeneration(); PatchSpecialQuest(); + ASMPatchFXLayersInit(); ASMPatchDealRoomVariants(); //PatchOverrideDataHandling(); PatchLevelGeneratorTryResizeEndroom(); @@ -215,10 +233,23 @@ void PerformASMPatches() { ZHL::Log("[ERROR] Error while applying an archive checksum skip\n"); }; + // This patch needs to be remade to include a toggle setting and fix glowing hourglass and the day before a release isn't the time for that //if (!ASMPatches::FixHushFXVeins()) { // ZHL::Log("[ERROR] Error while restoring Hush boss room veins FX\n"); //} - + + // Nightmare stuff. + PatchRoomSetDestructor(); +} + +void PatchRoomSetDestructor() { + HMODULE lib = GetModuleHandleA("libzhl.dll"); + FARPROC ourDestructor = GetProcAddress(lib, "??1RoomSet@@QAE@XZ"); + FARPROC gameDestructor = GetProcAddress(lib, "?destructor@RoomSet@@QAEXXZ"); + + ASMPatch patch; + patch.AddRelativeJump(gameDestructor); + sASMPatcher.FlatPatch(ourDestructor, &patch, true); } \ No newline at end of file diff --git a/repentogon/Patches/ASMPatches/ASMLevel.cpp b/repentogon/Patches/ASMPatches/ASMLevel.cpp index 5319b3488..ca926b2cd 100644 --- a/repentogon/Patches/ASMPatches/ASMLevel.cpp +++ b/repentogon/Patches/ASMPatches/ASMLevel.cpp @@ -5,6 +5,9 @@ #include "ASMLevel.h" #include "../../LuaInterfaces/Level.h" #include "../../LuaInterfaces/Room/Room.h" +#include "../Stages/StageManager.h" + +#include "../XMLData.h" std::bitset<36> generateLevels; @@ -175,25 +178,25 @@ void PatchSpecialQuest() { static const int TRAPDOOR_DEAL_SUBTYPE = 666; // Change subtype of trapdoor Devil and Angel room, after they are loaded in RoomConfig -HOOK_METHOD(RoomConfig, LoadStageBinary, (unsigned int Stage, unsigned int Mode) -> void) +HOOK_METHOD(RoomConfig, LoadStageBinary, (unsigned int Stage, unsigned int Mode) -> bool) { - super(Stage, Mode); + bool ret = super(Stage, Mode); - if (Stage != 0) + if (Stage == 0) { - return; - } - - //Change Trapdoor Subtype - for (int roomType = 14; roomType < 16; roomType++) - { - unsigned int doors = 0; - RoomConfigRoomPtrVector rooms = g_Game->_roomConfig.GetRooms(0, roomType, 13, 100, 100, 0, 20, &doors, 0, Mode); - for (RoomConfig_Room* p : rooms) + //Change Trapdoor Subtype + for (int roomType = 14; roomType < 16; roomType++) { - p->Subtype = TRAPDOOR_DEAL_SUBTYPE; + unsigned int doors = 0; + RoomConfigRoomPtrVector rooms = g_Game->_roomConfig.GetRooms(0, roomType, 13, 100, 100, 0, 20, &doors, 0, Mode); + for (RoomConfig_Room* p : rooms) + { + p->Subtype = TRAPDOOR_DEAL_SUBTYPE; + } } } + + return ret; } /* This function overrides the call to GetRandomRoom in InitDevilAngelRoom. @@ -324,6 +327,39 @@ void PatchOverrideDataHandling() { sASMPatcher.FlatPatch(patchAddr, &patch2); } +void __stdcall AdjustLevelStageBackdrop(FXLayers* fxlayers) { + StageManager& stageManager = StageManager::GetInstance(); + int stage = g_Game->GetStageID(false); + int backdrop = fxlayers->_backdropType; + + //printf("stage %d, overriden %d, id %d, token %s\n", stage, stageManager.stageState[stage].overriden, stageManager.stageState[stage].id, stageManager.stageState[stage].token.empty() ? "EMPTY" : stageManager.stageState[stage].token.c_str()); + + if (stageManager.IsStageOverriden(stage)) { + fxlayers->_levelStage = stageManager.stageForConfigId[stage] + 4; // to counter dumb math later on in xml parsing + fxlayers->_stageType = 1; + } + if (XMLStuff.BackdropData->backdropState.first == backdrop) { + fxlayers->_backdropType = XMLStuff.BackdropData->backdropState.second; + } + + fxlayers->_averagePlayerPos = Vector(0, 0); +} + +void ASMPatchFXLayersInit() { + SigScan scanner("f30f1106f30f1005????????f30f1146??e8"); // this->_averagePlayerPos = g_VectorZero + scanner.Scan(); + void* addr = scanner.GetAddress(); + printf("[REPENTOGON] Patching FXLayers::Init at %p\n", addr); + ASMPatch::SavedRegisters savedRegisters(ASMPatch::SavedRegisters::Registers::GP_REGISTERS, true); + ASMPatch patch; + patch.PreserveRegisters(savedRegisters) + .Push(ASMPatch::Registers::ESI) // FXLayers + .AddInternalCall(AdjustLevelStageBackdrop) + .RestoreRegisters(savedRegisters) + .AddRelativeJump((char*)addr + 0xc); + sASMPatcher.PatchAt(addr, &patch); +} + // https://docs.google.com/spreadsheets/d/1Y9SUTWnsVTrc_0f1vSZzqK6zc-1qttDrCG5kpDLrTvA/ void PatchTryResizeEndroomIncorrectDoorSlotsForLongWalls() { SigScan scanner("898424????????898c24????????898424????????898c24????????898424????????898c24????????898424????????898c24????????898424"); diff --git a/repentogon/Patches/ASMPatches/ASMLevel.h b/repentogon/Patches/ASMPatches/ASMLevel.h index 0a292e6c1..ccd82f762 100644 --- a/repentogon/Patches/ASMPatches/ASMLevel.h +++ b/repentogon/Patches/ASMPatches/ASMLevel.h @@ -3,6 +3,7 @@ void ASMPatchBlueWombCurse(); void ASMPatchVoidGeneration(); void PatchSpecialQuest(); +void ASMPatchFXLayersInit(); void ASMPatchDealRoomVariants(); void PatchOverrideDataHandling(); void PatchLevelGeneratorTryResizeEndroom(); diff --git a/repentogon/Patches/Stages/LuaStageManager.cpp b/repentogon/Patches/Stages/LuaStageManager.cpp new file mode 100644 index 000000000..f3ddb2ec6 --- /dev/null +++ b/repentogon/Patches/Stages/LuaStageManager.cpp @@ -0,0 +1,142 @@ +#include "IsaacRepentance.h" +#include "LuaCore.h" +#include "HookSystem.h" + +#include "../repentogon/Patches/XMLData.h" +#include "StageManager.h" + +LUA_FUNCTION(Lua_StageManager_LoadStage) { + StageManager& stageManager = StageManager::GetInstance(); + const char* name = luaL_checkstring(L, 1); + XMLAttributes xmlData = XMLStuff.StageData->GetNodeByName(name); + if (xmlData["basestage"].empty()) + return luaL_error(L, "No basestage for stage name %s", name); + + lua_pushboolean(L, stageManager.LoadStage(toint(xmlData["basestage"]), toint(xmlData["id"]))); + return 1; +} + +LUA_FUNCTION(Lua_StageManager_RestoreStage) { + StageManager& stageManager = StageManager::GetInstance(); + int id = (int)luaL_checkinteger(L, 1); + if (id < 0 || id > 36) { + return luaL_error(L, "id must be between 0 and 36, both inclusive (got %d)", id); + } + + lua_pushboolean(L, stageManager.RestoreStage(id)); + return 1; +} + +LUA_FUNCTION(Lua_StageManager_LoadBinary) { + StageManager& stageManager = StageManager::GetInstance(); + std::string path = luaL_checkstring(L, 1); + + RoomSet* set = stageManager.GetBinary(path, true); + + if (set == nullptr) { + lua_pushnil(L); + } + else + { + RoomSet** ud = (RoomSet**)lua_newuserdata(L, sizeof(RoomSet*)); + *ud = set; + luaL_setmetatable(L, lua::metatables::RoomConfigSetMT); + } + + return 1; +} + +LUA_FUNCTION(Lua_StageManager_GetBinary) { + StageManager& stageManager = StageManager::GetInstance(); + const char* id = luaL_checkstring(L, 1); + + std::unordered_map::const_iterator itr = stageManager.binaryMap.find(id); + + if (itr == stageManager.binaryMap.end()) { + lua_pushnil(L); + } + else + { + const RoomSet** ud = (const RoomSet**)lua_newuserdata(L, sizeof(RoomSet*)); + *ud = &itr->second; + luaL_setmetatable(L, lua::metatables::RoomConfigSetMT); + } + + return 1; +} + +LUA_FUNCTION(Lua_StageManager_AppendContentBinary) { + StageManager& stageManager = StageManager::GetInstance(); + RoomSet* roomSet; + if (lua_type(L, 1) == LUA_TSTRING) + { + std::string path = luaL_checkstring(L, 1); + roomSet = stageManager.GetBinary(path, true); + if (roomSet == nullptr) { + return luaL_error(L, "No binary exists for path \"%s\"!", path.empty() ? "" : path.c_str()); + } + } + else + { + roomSet = *lua::GetUserdata(L, 1, lua::metatables::RoomConfigSetMT); + } + std::string filepath = luaL_checkstring(L, 2); + + lua_pushboolean(L, stageManager.AppendContentBinary(roomSet, filepath)); + + return 1; +} + +LUA_FUNCTION(Lua_StageManager_IsStageOverriden) { + StageManager& stageManager = StageManager::GetInstance(); + int id = (int)luaL_checkinteger(L, 1); + + lua_pushboolean(L, stageManager.IsStageOverriden(id)); + return 1; +} + +LUA_FUNCTION(Lua_StageManager_ResetRoomWeights) { + StageManager& stageManager = StageManager::GetInstance(); + RoomSet* roomSet; + if (lua_type(L, 1) == LUA_TSTRING) + { + std::string path = luaL_checkstring(L, 1); + roomSet = stageManager.GetBinary(path, false); + if (roomSet == nullptr) { + return luaL_error(L, "No binary exists for path \"%s\"!", path.empty() ? "" : path.c_str()); + } + } + else + { + roomSet = *lua::GetUserdata(L, 1, lua::metatables::RoomConfigSetMT); + } + stageManager.ResetRoomWeights(roomSet); + + return 0; +} + +LUA_FUNCTION(Lua_StageManager_ResetAllRoomWeights) { + StageManager& stageManager = StageManager::GetInstance(); + stageManager.ResetAllRoomWeights(); + return 0; +} + +static void RegisterStageManager(lua_State* L) { + lua_newtable(L); + lua::TableAssoc(L, "LoadStage", Lua_StageManager_LoadStage); + lua::TableAssoc(L, "RestoreStage", Lua_StageManager_RestoreStage); + lua::TableAssoc(L, "LoadBinary", Lua_StageManager_LoadBinary); + lua::TableAssoc(L, "GetBinary", Lua_StageManager_GetBinary); + lua::TableAssoc(L, "AppendContentBinary", Lua_StageManager_AppendContentBinary); + lua::TableAssoc(L, "ResetRoomWeights", Lua_StageManager_ResetRoomWeights); + lua::TableAssoc(L, "ResetAllRoomWeights", Lua_StageManager_ResetAllRoomWeights); + lua::TableAssoc(L, "IsStageOverriden", Lua_StageManager_IsStageOverriden); + lua_setglobal(L, "StageManager"); +} + +HOOK_METHOD(LuaEngine, RegisterClasses, () -> void) { + super(); + + lua::LuaStackProtector protector(_state); + RegisterStageManager(_state); +} \ No newline at end of file diff --git a/repentogon/Patches/Stages/StageManager.cpp b/repentogon/Patches/Stages/StageManager.cpp new file mode 100644 index 000000000..3b156c30f --- /dev/null +++ b/repentogon/Patches/Stages/StageManager.cpp @@ -0,0 +1,437 @@ +#include "IsaacRepentance.h" +#include "Log.h" + +#include "StageManager.h" +#include "suffixes.h" + +#include +#include +#include + +/** + * Loads a stage binary from the specified path (relative to resources directory). + * + * @param path the file path of the stage binary to load + * + * @return a pointer to the loaded RoomSet, or nullptr if loading fails + */ +RoomSet* StageManager::LoadBinary(std::string& path) { + RoomConfig* roomConfig = g_Game->GetRoomConfig(); + ZHL::Logger logger(true); + logger.Log("[INFO] StageManager::LoadBinary: Loading stage binary \"%s\"\n", path.c_str()); + + RoomSet newSet; + RoomConfig_Stage* buffer = roomConfig->_stages + BUFFER_STAGEID; + newSet._filepath = path; + buffer->_rooms[0] = newSet; + + // to look better in the log + buffer->_displayName = "(binary) \"" + path + "\""; + + bool res = roomConfig->LoadStageBinary(BUFFER_STAGEID, 0); + + if (res) { + RoomSet* ret = &(binaryMap.find(path)->second); + logger.Log("[INFO] StageManager::LoadBinary: Loaded successfully at pointer %p\n", ret); + return ret; + } + + logger.Log("[WARNING] StageManager::LoadBinary: Failed to load binary \"%s\"!\n", path.c_str()); + return nullptr; +}; + +/** + * Retrieves a RoomSet from the binary cache. + * + * @param path The file path of the binary to retrieve. + * @param loadIfUncached If true, loads the binary from file if it is not in the cache. + * + * @return A pointer to the retrieved RoomSet, or nullptr if not found. + */ +RoomSet* StageManager::GetBinary(std::string& path, bool loadIfUncached) { + ZHL::Logger logger(true); + logger.Log("[INFO] StageManager::GetBinary: Attempting to retrieve binary \"%s\" from cache\n", path.c_str()); + + std::unordered_map::iterator itr = binaryMap.find(path); + + if (itr != binaryMap.end()) { + RoomSet* ret = &(itr->second); + logger.Log("[INFO] StageManager::GetBinary: Retrieved successfully from pointer %p\n", ret); + return ret; + } + + logger.Log("[INFO] StageManager::GetBinary: Binary is not in cache, %s\n", loadIfUncached ? "loading" : "returning nullptr"); + + if (loadIfUncached) { + return LoadBinary(path); + } + return nullptr; +}; + +/** + * Checks if a binary is loaded in the cache. + * + * @param path the file path of the binary to check + * + * @return true if the binary is loaded, false otherwise + */ +bool StageManager::IsBinaryLoaded(std::string& path) { + std::unordered_map::const_iterator itr = binaryMap.find(path); + return itr != binaryMap.end(); +}; + +/** + * Retrieves a StageDefinition from the cache, building it if it does not exist. + * + * @param stageId the ID of the stage to retrieve the definition for + * + * @return a reference to the retrieved StageDefinition + */ +StageDefinition& StageManager::GetStageDefinition(int stageId) { + ZHL::Logger logger(true); + logger.Log("[INFO] StageManager::GetStageDefinition: Attempting to retrieve definition for id \"%d\" from cache\n", stageId); + + std::unordered_map::iterator itr = stageDefinitionMap.find(stageId); + + if (itr != stageDefinitionMap.end()) { + StageDefinition ret = (itr->second); + logger.Log("[INFO] StageManager::GetStageDefinition: Retrieved successfully from pointer %p\n", ret); + return ret; + } + + logger.Log("[INFO] StageManager::GetStageDefinition: Definition is not in cache, building\n"); + stageDefinitionMap.insert({ stageId, StageDefinition(stageId) }); + return stageDefinitionMap.find(stageId)->second; +} + +/** + * Replaces the members of a RoomConfig_Stage object with the members of a StageDefinition. + * + * @param stage the RoomConfig_Stage object to replace the stage definition for + * @param newDef the new StageDefinition to replace the existing one with + * + * @return none + */ +static void ReplaceStageDefinition(RoomConfig_Stage* stage, StageDefinition& newDef) { + stage->_displayName = newDef.name; + stage->_musicId = newDef.musicId; + stage->_backdrop = newDef.backdropId; + stage->_playerSpot = newDef.playerSpot; + stage->_bossSpot = newDef.bossSpot; + stage->_suffix = newDef.suffix; +} + +// fuck youuuuuuu +inline char* StringToChar(std::string& str) { + return str.empty() ? "" : str.c_str(); +} + +/** + * Loads a new stage into the current game configuration. + * + * This function replaces the RoomSet of the current stage with a new one, + * either by loading a new binary or by reusing an existing one from the cache. + * It also updates the RoomConfig_Stage object with the new stage definition. + * + * Setting the same id on both params will reset the RoomConfig_Stage to its default state. + * + * @param configId The ID of the RoomConfig_Stage to be replaced. + * @param newId The ID of the new stage to be loaded. + * + * @return True if the stage was successfully loaded or already loaded, false if no binary was able to be loaded. + */ +bool StageManager::LoadStage(int configId, int newId) { + ZHL::Logger logger(true); + logger.Log("[INFO] StageManager::LoadStage: Attempting to load stage id %d into config id %d\n", newId, configId); + + if (stageForConfigId[configId] == newId) { + logger.Log("[INFO] StageManager::LoadStage: Stage id %d is already loaded into config id %d\n", newId, configId); + return true; + } + + RoomConfig* roomConfig = g_Game->GetRoomConfig(); + + // This also builds the StageDefinition of the existing and new stages + StageDefinition oldDef = StageDefinition(configId); + StageDefinition newDef = StageDefinition(newId); + + logger.Log("[INFO] StageManager::LoadStage: [CURRENT] name %s, path %s, greed path %s, playerSpot %s, bossSpot %s, suffix %s, musicId %d, backDropId %d\n", StringToChar(oldDef.name), StringToChar(oldDef.binary), StringToChar(oldDef.greedBinary), StringToChar(oldDef.playerSpot), StringToChar(oldDef.bossSpot), StringToChar(oldDef.suffix), oldDef.musicId, oldDef.backdropId); + logger.Log("[INFO] StageManager::LoadStage: [NEW] name %s, path %s, greed path %s, playerSpot %s, bossSpot %s, suffix %s, musicId %d, backDropId %d\n", StringToChar(newDef.name), StringToChar(newDef.binary), StringToChar(newDef.greedBinary), StringToChar(newDef.playerSpot), StringToChar(newDef.bossSpot), StringToChar(newDef.suffix), newDef.musicId, newDef.backdropId); + + RoomConfig_Stage& stage = roomConfig->_stages[configId]; + std::unordered_map::const_iterator itr; + bool success = false; + for (size_t i = 0; i < 2; i++) + { + std::string& path = i ? newDef.greedBinary : newDef.binary; + logger.Log("[INFO] Processing RoomSet \"%s\" (mode %d)\n", path.c_str(), i); + if (stage._rooms[i]._loaded) + { + logger.Log("[INFO] already loaded\n"); + itr = binaryMap.find(stage._rooms[i]._filepath); + if (itr == binaryMap.end()) { + // somehow hasn't been inserted yet, do so + logger.Log("[WARNING] existing RoomSet not cached yet, inserting \"%s\"\n", stage._rooms[i]._filepath.c_str()); + binaryMap.insert({ stage._rooms[i]._filepath, stage._rooms[i] }); + } + } + /* + else + { + logger.Log("[INFO] \"%s\" not loaded yet, attempting...\n", stage._rooms[i]._filepath.c_str()); + RoomSet* baseSet = LoadBinary(&stage._rooms[i]._filepath); + if (baseSet != nullptr) + binaryMap.insert({ stage._rooms[i]._filepath, *baseSet }); + + } + */ + + RoomSet* set = GetBinary(path, false); + if (set != nullptr) { + logger.Log("[INFO] replacing with existing RoomSet \"%s\"\n", set->_filepath.c_str()); + stage._rooms[i] = *set; + success = true; + } + else + { + logger.Log("[INFO] loading new binary \"%s\" for RoomSet replacement\n", path.c_str()); + RoomSet* newBinary = LoadBinary(path); + if (newBinary != nullptr) { + stage._rooms[i] = *newBinary; + success = true; + } + } + } + + if (success) { + ReplaceStageDefinition(&stage, newDef); + + stageForConfigId[configId] = newId; + logger.Log("[INFO] StageManager::LoadStage: successfully assigned stage id %d to config id %d\n", newId, configId); + return true; + } + else + { + logger.Log("[ERROR] StageManager::LoadStage: could not replace the RoomSet of either mode!\n"); + } + + return false; +}; + +/** + * Restores the specified stage to its default state. + * + * @param configId The stage to restore. + * + * @return True if the stage was restored successfully, false otherwise. + */ +bool StageManager::RestoreStage(int configId) { + return LoadStage(configId, configId); +} + +/** + * Attempts to append the specified content binary to the specified RoomSet. + * + * @param roomSet The RoomSet to append the binary to. + * @param binary The binary to append. + * + * @return True if the room count has increased after appending the binary, false otherwise. + */ +bool StageManager::AppendContentBinary(RoomSet* roomSet, std::string& binary) { + RoomConfig* roomConfig = g_Game->GetRoomConfig(); + ModManager* modManager = g_Manager->GetModManager(); + roomConfig->_stages[BUFFER_STAGEID]._id = BUFFER_STAGEID; + RoomSet* buffer = &roomConfig->_stages[BUFFER_STAGEID]._rooms[0]; + unsigned int roomCount = buffer->_count; + buffer->_filepath = binary; + // TODO: mark this binary as appended to the input RoomSet so subsequent attempts can be cancelled + modManager->UpdateRooms(BUFFER_STAGEID, 0); + buffer->_filepath = roomSet->_filepath; + *roomSet = *buffer; + return roomCount < buffer->_count; +} + +void StageManager::ResetRoomWeights(RoomSet* set) { + for (unsigned int i = 0; i < set->_count; i++) { + set->_configs[i].Weight = set->_configs[i].InitialWeight; + } +}; + +void StageManager::ResetAllRoomWeights() { + for (auto i = binaryMap.begin(); i != binaryMap.end(); i++) { + ResetRoomWeights(&i->second); + } +} + +namespace fs = std::filesystem; +/** + * Checks if a given filename is present in a blacklist of vanilla STB files. + * + * @param name The filename to check. + * @param blacklist An array of blacklisted vanilla STB filenames. + * + * @return True if the filename is in the blacklist, false otherwise. + */ +static bool CheckVanillaStb(const fs::path& name, const std::array& blacklist) +{ + for (const auto& blacklisted : blacklist) { + ZHL::Log("Checking %s against %s\n", name.string().c_str(), blacklisted); + if (name.string() == blacklisted) { + return true; + } + } + return false; +} + +/** + * Retrieves a map of stage binary files in the specified mod directory, excluding vanilla STB files. + * + * @param directory The directory to search for stage binary files. + * @param idCounter A counter used to assign unique IDs to the found files. + * + * @return A map of file IDs to their corresponding relative file paths. + */ +std::unordered_map StageManager::GetStageBinaryFiles(const fs::path& directory, size_t& idCounter) { + std::unordered_map matchingFiles; + + for (const auto& entry : fs::recursive_directory_iterator(directory)) { + if (entry.is_regular_file() && entry.path().extension() == ".stb") { + if (!CheckVanillaStb(entry.path().filename(), vanillaStbs)) + matchingFiles.insert({ fs::relative(entry.path(), directory.parent_path()).string(), idCounter++ }); + } + } + + return matchingFiles; +} + +/** + * Builds a map of stage binary files from the resources directories of enabled mods. + * + * @param none + * + * @return none + * + * @throws none + */ +void StageManager::BuildFilenameMap() { + vector_ModEntryPointer mods = g_Manager->GetModManager()->_mods; + size_t idCounter = 0; + + filenameMap.clear(); + + for (const auto& entry : mods) { + if (entry->_enabled) { + fs::path resourcesRoomsPath = (fs::path(entry->_resourcesDirectory) / "rooms"); + if (fs::exists(resourcesRoomsPath) && fs::is_directory(resourcesRoomsPath)) { + std::unordered_map files = GetStageBinaryFiles(resourcesRoomsPath, idCounter); + filenameMap.merge(files); + } + } + } +} + +// Initialize StageManager +HOOK_METHOD(ModManager, LoadConfigs, () -> void) { + super(); + StageManager& stageManager = StageManager::GetInstance(); + for (int i = 0; i < 37; i++) { + stageManager.stageForConfigId[i] = i; + } + stageManager.BuildFilenameMap(); + for (const auto& entry : stageManager.filenameMap) { + ZHL::Log("Found file %s with id %d\n", entry.first.c_str(), entry.second); + } +} + +// Handle RoomSet cacheing +HOOK_METHOD(RoomConfig, LoadStageBinary, (unsigned int id, unsigned int mode) -> bool) { + RoomConfig_Stage& stage = this->_stages[id]; + RoomSet& set = stage._rooms[mode]; + std::string& path = set._filepath; + StageManager& stageManager = StageManager::GetInstance(); + + // if we already loaded this binary, use the cached version + std::unordered_map::const_iterator itr = stageManager.binaryMap.find(path); + if (itr != stageManager.binaryMap.end()) { + stringstream message; + message << "[RoomConfig] stage " << id << ": " << stage._displayName << " (mode " << mode << ") already loaded from binary \"" << set._filepath << "\"\n"; + KAGE::LogMessage(0, message.str().c_str()); + set = itr->second; + return true; + } + bool res = super(id, mode); + + // cache the loaded binary + if (res) { + stringstream message; + message << "[RoomConfig] caching binary \"" << set._filepath << "\"\n"; + KAGE::LogMessage(0, message.str().c_str()); + stageManager.binaryMap.insert({ path, set }); + if (stage._suffix.empty()) { + stage._suffix = suffixes[id]; + } + } + + return res; +} + +HOOK_METHOD(RoomConfig_Stage, unload, () -> void) { + StageManager& stageManager = StageManager::GetInstance(); + std::unordered_map::const_iterator itr; + bool restored = false; + for (unsigned int i = 0; i < 2; i++) { + itr = stageManager.binaryMap.find(this->_rooms[i]._filepath); + if (itr != stageManager.binaryMap.end()) { + stageManager.binaryMap.erase(itr); + stringstream message; + message << "removed binary \"" << this->_rooms[i]._filepath << "\" from StageManager cache.\n"; + KAGE::LogMessage(0, message.str().c_str()); + restored = true; + } + } + if (restored) { + StageDefinition &def = stageManager.GetStageDefinition(this->_id); + + ReplaceStageDefinition(this, def); + + this->_rooms[0]._filepath = def.binary; + this->_rooms[1]._filepath = def.greedBinary; + + stageManager.stageForConfigId[this->_id] = this->_id; + + // we already handled destroying the RoomSet above, don't do it again + return; + } + + super(); +} + +// Force use our custom FXLayers if in a custom stage. A patch in ASMLevel handles setting the backdrop before this gets called. +HOOK_STATIC(FXLayers, check_fxlayer_match, (int stage, int altStages, int compLevelStage, int compStageType) -> bool, __cdecl) { + if (stage >= 41) { + return stage == compLevelStage; + } + return super(stage, altStages, compLevelStage, compStageType); +} + +// Reset room weights on starting a new game +HOOK_METHOD(Game, Start, (int playertype, int challenge, Seeds seeds, unsigned int difficulty) -> void) { + StageManager& stageManager = StageManager::GetInstance(); + stageManager.ResetAllRoomWeights(); + super(playertype, challenge, seeds, difficulty); +} +HOOK_METHOD(Game, NetStart, (void* unk, int challenge, Seeds seed, unsigned int difficulty, GameState* state) -> void) { + StageManager& stageManager = StageManager::GetInstance(); + stageManager.ResetAllRoomWeights(); + super(unk, challenge, seed, difficulty, state); +} +HOOK_METHOD(Game, StartDebug, (int levelStage, int stageType, int difficulty, std_string* filepath) -> void) { + StageManager& stageManager = StageManager::GetInstance(); + stageManager.ResetAllRoomWeights(); + super(levelStage, stageType, difficulty, filepath); +} +HOOK_METHOD(Game, StartFromRerunState, (GameState* state) -> void) { + StageManager& stageManager = StageManager::GetInstance(); + stageManager.ResetAllRoomWeights(); + super(state); +} \ No newline at end of file diff --git a/repentogon/Patches/Stages/StageManager.h b/repentogon/Patches/Stages/StageManager.h new file mode 100644 index 000000000..969069dc0 --- /dev/null +++ b/repentogon/Patches/Stages/StageManager.h @@ -0,0 +1,118 @@ +#pragma once +#include "IsaacRepentance.h" +#include +#include +#include +#include + +#include "../repentogon/Patches/XMLData.h" + +namespace fs = std::filesystem; + +class StageDefinition { +public: + StageDefinition(int stageId) { + XMLAttributes xmlData = XMLStuff.StageData->GetNodeById(stageId); + + std::string gfxRoot = xmlData["bossgfxroot"]; + binary = xmlData["root"] + xmlData["path"]; + greedBinary = xmlData["greedroot"] + xmlData["path"]; + playerSpot = gfxRoot + xmlData["playerspot"]; + bossSpot = gfxRoot + xmlData["bossspot"]; + displayName = xmlData["displayname"].empty() ? xmlData["name"] : xmlData["displayname"]; + suffix = xmlData["suffix"]; + name = xmlData["name"]; + musicId = toint(xmlData["music"]); + backdropId = toint(xmlData["backdrop"]); + } + + std::string binary; + std::string greedBinary; + std::string gfxRoot; + std::string playerSpot; + std::string bossSpot; + std::string displayName; + std::string suffix; + std::string name; + int musicId; + int backdropId; +}; + +class StageManager +{ +private: + + StageManager() {} + std::unordered_map GetStageBinaryFiles(const fs::path&, size_t&); + const std::array vanillaStbs = { + "00.special rooms.stb", + "01.basement.stb", + "02.cellar.stb", + "03.burning basement.stb", + "04.caves.stb", + "05.catacombs.stb", + "06.flooded caves.stb", + "07.depths.stb", + "08.necropolis.stb", + "09.dank depths.stb", + "10.womb.stb", + "11.utero.stb", + "12.scarred womb.stb", + "13.blue womb.stb", + "14.sheol.stb", + "15.cathedral.stb", + "16.dark room.stb", + "17.chest.stb", + "24.the shop.stb", + "25.ultra greed.stb", + "26.the void.stb", + "26.the void_ex.stb", + "27.downpour.stb", + "28.dross.stb", + "29.mines.stb", + "30.ashpit.stb", + "31.mausoleum.stb", + "32.gehenna.stb", + "33.corpse.stb", + "34.mortis.stb", + "35.home.stb", + "36.backwards.stb" + }; + + +public: + static StageManager& GetInstance() { + static StageManager instance; + return instance; + } + + // New binaries are loaded into this unused RoomConfig_Stage id + const unsigned int BUFFER_STAGEID = 23; + + //TODO: merge these two maps + + // Associates a .stb filepath to its respective RoomSet + std::unordered_map binaryMap; + + // Associates a .stb filepath to an id for later restoration + std::unordered_map filenameMap; + + // Associates a stage id to a struct of definitions for its RoomConfig_Stage + std::unordered_map stageDefinitionMap; + + // Associates the config id of a stage to its current stage id + int stageForConfigId[37]; + inline bool IsStageOverriden(int id) const{ + return stageForConfigId[id] != id; + } + RoomSet* LoadBinary(std::string& path); + RoomSet* GetBinary(std::string& path, bool loadIfUncached); + bool AppendContentBinary(RoomSet* roomSet, std::string& binary); + bool IsBinaryLoaded(std::string& path); + bool LoadStage(int configId, int newId); + bool RestoreStage(int configId); + void ResetRoomWeights(RoomSet* set); + void ResetAllRoomWeights(); + StageDefinition& StageManager::GetStageDefinition(int stageId); + void BuildFilenameMap(); +}; \ No newline at end of file diff --git a/repentogon/Patches/Stages/StageSwitching.cpp b/repentogon/Patches/Stages/StageSwitching.cpp new file mode 100644 index 000000000..bb616a3dc --- /dev/null +++ b/repentogon/Patches/Stages/StageSwitching.cpp @@ -0,0 +1,147 @@ +#include "IsaacRepentance.h" +#include "LuaCore.h" +#include "HookSystem.h" +#include "Log.h" + +#include "../repentogon/Patches/XMLData.h" +#include "StageManager.h" +#include "../StagesStuff.h" + +static bool IsSecondFloor(int stageid) { + // God I wish there was an easier way to do this + return (stageid == 2) || (stageid == 4) || (stageid == 6) || (stageid == 8) + || (g_Game->_curses & (1 << 1)); //has curse XL +} + +extern int toint(const string& str); +extern tuple GetSetStage(int stageid, bool secondfloor); + +static tuple ConsoleStageIdToTuple(const string& input) { + std::string numberPart; + int letterValue = 0; + for (char c : input) { + if (isdigit(c)) { + numberPart += c; + } + else { + if (isalpha(c)) { + int x = toint(numberPart); + int y = c - 'a' + 1; + if (y >= 3) + y += 1; + return { x , y }; + } + } + } + int y = toint(numberPart); + return { y ,0 }; +} + +static int GetStbTypeFromTuple(tuple id) { + auto it = std::find_if(std::begin(stageidtotuple), std::end(stageidtotuple), + [&id](auto&& p) { return p.second == id; }); + + if (it == std::end(stageidtotuple)) + return -1; + + return it->first; +} + +int lastconfigId = 0; +int overloadLevelStage = 0; +int overloadStageType = 0; + +extern std::vector ParseCommand2(const std::string& command, int size); + +HOOK_METHOD(Console, RunCommand, (std_string& in, std_string* out, Entity_Player* player)-> void) { + if (in.rfind("stage", 0) == 0) { + ZHL::Logger logger(true); + std::vector cmdlets = ParseCommand2(in, 2); + tuple id = ConsoleStageIdToTuple(cmdlets[1]); + + logger.Log("recieved stage %s, calculated id %d, %d, of which we have %d of\n", cmdlets[1].c_str(), get<0>(id), get<1>(id), XMLStuff.StageData->bystagealt.count(id)); + + // do we have a stage registered under that id? + if (XMLStuff.StageData->bystagealt.count(id) > 0) { + overloadLevelStage = get<0>(id); + overloadStageType = get<1>(id); + in = "stage 1"; + } + } + super(in, out, player); +} + + +HOOK_METHOD(Level, SetStage, (int levelStage, int stageType)-> void) { + StageManager& stageManager = StageManager::GetInstance(); + ZHL::Logger logger(true); + + if (overloadLevelStage > 0) + levelStage = overloadLevelStage; + + if (overloadStageType > 0) + stageType = overloadStageType; + + tuple idx = { levelStage, stageType }; + logger.Log("[INFO] node count for %d, %d : %d\n", levelStage, stageType, XMLStuff.StageData->bystagealt.count(idx)); + if (XMLStuff.StageData->bystagealt.count(idx) > 0) { + XMLAttributes& targetstage = XMLAttributes(XMLStuff.StageData->nodes[XMLStuff.StageData->bystagealt[idx]]); + int nodeId = toint((targetstage)["id"]); + int configId = toint((targetstage)["basestage"]); + + std::string stage = to_string(levelStage); + std::string stage2 = to_string(stageType); + std::string stage3 = to_string(nodeId); + std::string stage4 = to_string(configId); + + logger.Log("[INFO] SetStage: level %d, stage %d, node %d, config %d\n", levelStage, stageType, nodeId, configId); + + stageManager.LoadStage(configId, nodeId); + + tuple superArgs = GetSetStage(configId, IsSecondFloor(levelStage)); + levelStage = get<0>(superArgs); + stageType = get<1>(superArgs); + + logger.Log("[INFO] SetStage: got set stage %d, %d, second floor %s\n", levelStage, stageType, IsSecondFloor(levelStage) ? "true" : "false"); + + logger.Log("done\n"); + } + // dark home has special behavior, because of course it does... + else if (levelStage != 13 || stageType != 1) + { + logger.Log("[ERROR] SetStage: found no node for level %d, stage %d!\n", levelStage, stageType); + return; + } + + // last ditch effort to avoid disaster + if (levelStage == 0) { + logger.Log("[ERROR] SetStage: attempted to set stage to 0! defaulting to basement\n"); + levelStage = 1; + stageType = 0; + + } + + super(levelStage, stageType); + + overloadLevelStage = 0; + overloadStageType = 0; +} + +HOOK_METHOD(RoomConfig, LoadStageBinary, (unsigned int stage, unsigned int mode) -> bool) { + if (stage == 36) { + KAGE::LogMessage(1, "[RoomConfig] LoadStageBinary: force setting stage 36 to backwards\n"); + + std::string filepath = this->_stages[stage]._rooms[mode]._filepath; + std::string name = this->_stages[stage]._displayName; + this->_stages[stage]._displayName = "(backwards)"; + this->_stages[stage]._rooms[mode]._filepath = "rooms/36.backwards.xml"; + + bool res = super(stage, mode); + + this->_stages[stage]._displayName = name; + this->_stages[stage]._rooms[mode]._filepath = filepath; + + return res; + } + return super(stage, mode); +} \ No newline at end of file diff --git a/repentogon/Patches/Stages/suffixes.h b/repentogon/Patches/Stages/suffixes.h new file mode 100644 index 000000000..ca5777c6d --- /dev/null +++ b/repentogon/Patches/Stages/suffixes.h @@ -0,0 +1,80 @@ +#pragma once + +const char* suffixes[36] = { + "", + "_basement", + "_cellar", + "_burningbasement", + "_caves", + "_catacombs", + "_downpour", + "_depths", + "_necropolis", + "_dankdepths", + "_womb", + "_utero", + "_scarredwomb", + "_bluewomb", + "_sheol", + "_cathedral", + "_darkroom", + "_chest", + "", + "", + "", + "", + "", + "", + "", + "", + "", // void doesn't work, local stage ids are used + "_downpour", + "_dross", + "_mines", + "_ashpit", + "_mausoleum", + "_gehenna", + "_corpse", + "", // there will never be a mortis + "_home" +}; + +const char* tokens[37] = { + "SPECIAL_ROOMS", + "BASEMENT", + "CELLAR", + "BURNING_BASEMENT", + "CAVES", + "CATACOMBS", + "FLOODED_CAVES", + "DEPTHS", + "NECROPOLIS", + "DANK_DEPTHS", + "WOMB", + "UTERO", + "SCARRED_WOMB", + "BLUE_WOMB", + "SHEOL", + "CATHEDRAL", + "DARK_ROOM", + "CHEST", + "", + "", + "", + "", + "", + "", + "", + "", + "THE_VOID", + "DOWNPOUR", + "DROSS", + "MINES", + "ASHPIT", + "MAUSOLEUM", + "GEHENNA", + "CORPSE", + "MORTIS", // there will never be a mortis + "HOME" + "ASCENT" +}; \ No newline at end of file diff --git a/repentogon/Patches/StagesStuff.cpp b/repentogon/Patches/StagesStuff.cpp index 6f2bf8f18..67e7195c3 100644 --- a/repentogon/Patches/StagesStuff.cpp +++ b/repentogon/Patches/StagesStuff.cpp @@ -115,163 +115,6 @@ string ogstagespath; int queuedstage = 0; int queuedalt = 0; int lastrequest = 0; -/* -HOOK_STATIC(RoomConfig, GetStageID, (unsigned int LevelStage, unsigned int StageType, unsigned int Mode)-> unsigned int, __cdecl) { - unsigned int stageid = super(LevelStage,StageType, Mode); - //printf("getstage: %d \n", stageid); - return stageid; -} -*/ -int lastparentstage = 0; -int setstageoverloadid = 0; -int setstageoverloadalt = 0; -/* -HOOK_METHOD(Level, SetStage, (int a, int b)-> void) { - int stageid = a; - int alt = b; - if (setstageoverloadid > 0) { stageid = setstageoverloadid; setstageoverloadid = 0; } - if (setstageoverloadalt > 0) { alt = setstageoverloadalt; setstageoverloadalt = 0; } - char* xml = new char[ogstagespath.length() + 1]; - strcpy(xml, ogstagespath.c_str()); - tuple idx = { stageid,alt }; - if (XMLStuff.StageData->bystagealt.count(idx) > 0) { - XMLAttributes* targetstage = new XMLAttributes(XMLStuff.StageData->nodes[XMLStuff.StageData->bystagealt[idx]]); - int parentstage = toint((*targetstage)["basestage"]); - if (parentstage == 0) { parentstage = 1; } - queuedhackyxmlvalue = stageid; - queuedhackyxmltarget = parentstage; - queuedhackyxmlmaxval = 36; - //if (lastparentstage != stageid) { - for (int i = 0; i <= 36; i++) { - g_Game->GetRoomConfig()->UnloadStage(i); - } - g_Game->GetRoomConfig()->LoadStages(xml); - //} - printf("setstageX: %d %d \n", stageid, alt); - tuple setstg = GetSetStage(parentstage, IsOnSecondFloor()); - super(get<0>(setstg), get<1>(setstg)); - printf("done"); - lastparentstage = get<0>(setstg); - setstg; - } - else if (lastparentstage == get<0>(GetSetStage(a, IsOnSecondFloor()))){ - //no = true; - //for (int i = 0; i <= 36; i++) { - g_Game->GetRoomConfig()->UnloadStage(stageid); - //} - g_Game->GetRoomConfig()->LoadStages(xml); - super(stageid, alt); - lastparentstage = 0; - } - else { - super(stageid, alt); - } - queuedhackyxmlvalue = 0; - queuedhackyxmltarget = 0; - mclear(xml); -} -*/ -/* -HOOK_METHOD(RoomConfig, LoadStages, (char* xmlpath)-> void) { - if (ogstagespath.length() == 0) { - ogstagespath = xmlpath; - } - printf("stagexml: %s \n", xmlpath); - super(xmlpath); -} -*/ - - -tuple ConsoleStageIdToTuple(const string& input) { - string* numberPart = new string(""); - int letterValue = 0; - for (char c : input) { - if (isdigit(c)) { - *numberPart += c; - } - else { - if (isalpha(c)) { - int x = toint(*numberPart); - delete numberPart; - return { x ,c - 'a' + 1 }; - } - } - } - int y = toint(*numberPart); - delete numberPart; - return { y ,0 }; -} - -extern std::vector ParseCommand2(const std::string& command, int size); - - -HOOK_METHOD(Console, RunCommand, (std_string& in, std_string* out, Entity_Player* player)-> void) { - if (in.rfind("stage ", 0) == 0) { - std::vector cmdlets = ParseCommand2(in, 2); - if (cmdlets.size() > 1) { - tuple id = ConsoleStageIdToTuple(cmdlets[1]); - if (XMLStuff.StageData->bystagealt.count(id) == 0) { //stage 14 works without intervention, lol - super(in, out, player); - return; - } - else { - //super(in, out, player); //we still run it just in case I dunno, does nothing anyway - setstageoverloadid = get<0>(id); - setstageoverloadalt = get<1>(id); - g_Game->GetConsole()->RunCommand(string("stage 1"), out, player); - return; - } - } - } - super(in, out, player); -} - -const char* suffixes[35] = { - "_basement", - "_cellar", - "_burningbasement", - "_caves", - "_catacombs", - "_downpour", // rip - "_depths", - "_necropolis", - "_dankdepths", - "_womb", - "_utero", - "_scarredwomb", - "_bluewomb", - "_sheol", - "_cathedral", - "_darkroom", - "_chest", - "", - "", - "", - "", - "", - "", - "", - "", - "", // void doesn't work, local stage ids are used - "_downpour", - "_dross", - "_mines", - "_ashpit", - "_mausoleum", - "_gehenna", - "_corpse", - "", // there will never be a mortis - "_home" -}; - -// helper for asm patch -HOOK_METHOD(RoomConfig, LoadStageBinary, (unsigned int Stage, unsigned int Mode) -> void) { - super(Stage, Mode); - - if (Stage != 0 && Stage < 36) { - this->_stages[Stage]._suffix = suffixes[Stage-1]; - } -} LUA_FUNCTION(Lua_SetCurrentFloorMusic) { diff --git a/repentogon/Patches/StagesStuff.h b/repentogon/Patches/StagesStuff.h index b674515f8..c3fb11ea7 100644 --- a/repentogon/Patches/StagesStuff.h +++ b/repentogon/Patches/StagesStuff.h @@ -22,42 +22,46 @@ using namespace std; extern void SetCurrentFloorMusic(int etype); +extern tuple GetSetStage(int stageid, bool secondfloor); inline unordered_map > stageidtotuple; + +// NOTE: StageType 3 is unused because it's deprecated (StageType.STAGETYPE_GREEDMODE) inline void initstagetotuple() { //Vanilla - stageidtotuple[1] = { 1,0 }; - stageidtotuple[2] = { 1,1 }; - stageidtotuple[3] = { 1,2 }; - stageidtotuple[4] = { 3,0 }; - stageidtotuple[5] = { 3,1 }; - stageidtotuple[6] = { 1,2 }; - stageidtotuple[7] = { 5,0 }; - stageidtotuple[8] = { 5,1 }; - stageidtotuple[9] = { 5,2 }; - stageidtotuple[10] = { 7,0 }; - stageidtotuple[11] = { 7,1 }; - stageidtotuple[12] = { 7,2 }; - stageidtotuple[13] = { 9,0 }; - stageidtotuple[14] = { 10,0 }; - stageidtotuple[15] = { 10,1 }; - stageidtotuple[16] = { 11,0 }; - stageidtotuple[17] = { 11,1 }; - stageidtotuple[26] = { 12,0 }; + stageidtotuple[1] = { 1,0 }; // Basement + stageidtotuple[2] = { 1,1 }; // Cellar + stageidtotuple[3] = { 1,2 }; // Burning Basement + stageidtotuple[4] = { 3,0 }; // Caves + stageidtotuple[5] = { 3,1 }; // Catacombs + stageidtotuple[6] = { 3,2 }; // Flooded Caves + stageidtotuple[7] = { 5,0 }; // Depths + stageidtotuple[8] = { 5,1 }; // Necropolis + stageidtotuple[9] = { 5,2 }; // Dank Depths + stageidtotuple[10] = { 7,0 }; // Womb + stageidtotuple[11] = { 7,1 }; // Utero + stageidtotuple[12] = { 7,2 }; // Scarred Womb + stageidtotuple[13] = { 9,0 }; // Blue Womb + stageidtotuple[14] = { 10,0 }; // Sheol + stageidtotuple[15] = { 10,1 }; // Cathedral + stageidtotuple[16] = { 11,0 }; // Dark Room + stageidtotuple[17] = { 11,1 }; // Chest + stageidtotuple[26] = { 12,0 }; // Void //Vanilla //Greed - stageidtotuple[24] = { 6,0 }; - stageidtotuple[25] = { 7,0 }; + //stageidtotuple[24] = { 6,0 }; + //stageidtotuple[25] = { 7,0 }; //Greed //Repentance - stageidtotuple[27] = { 1,4 }; - stageidtotuple[28] = { 1,5 }; - stageidtotuple[29] = { 3,4 }; - stageidtotuple[30] = { 3,5 }; - stageidtotuple[31] = { 5,4 }; - stageidtotuple[32] = { 5,5 }; - stageidtotuple[33] = { 7,4 }; - stageidtotuple[35] = { 13,0 }; + stageidtotuple[27] = { 1,4 }; // Downpour + stageidtotuple[28] = { 1,5 }; // Dross + stageidtotuple[29] = { 3,4 }; // Mines + stageidtotuple[30] = { 3,5 }; // Ashpit + stageidtotuple[31] = { 5,4 }; // Mausoleum + stageidtotuple[32] = { 5,5 }; // Gehenna + stageidtotuple[33] = { 7,4 }; // Corpse + stageidtotuple[34] = { 7,5 }; // Mortis + stageidtotuple[35] = { 13,0 }; // Home //Repentance } diff --git a/repentogon/Patches/VanillaTweaks.cpp b/repentogon/Patches/VanillaTweaks.cpp index 9293df057..804e8121c 100644 --- a/repentogon/Patches/VanillaTweaks.cpp +++ b/repentogon/Patches/VanillaTweaks.cpp @@ -19,10 +19,10 @@ HOOK_METHOD(Entity_Slot, TakeDamage, (float Damage, unsigned long long DamageFla // Allow Void to have its own rooms. // By default, the void path is "rooms/01.Basement.xml" which is not ideal! // Redirect to "rooms/26.The Void_ex.xml" since the game already has a "rooms/26.The Void.xml" that hasn't been tested. -HOOK_METHOD(RoomConfig, LoadStageBinary, (unsigned int Stage, unsigned int Mode) -> void) { +HOOK_METHOD(RoomConfig, LoadStageBinary, (unsigned int Stage, unsigned int Mode) -> bool) { if (Stage == 26 && g_Game->GetRoomConfig()->_stages[26]._rooms[Mode]._filepath == "rooms/01.Basement.xml") g_Game->GetRoomConfig()->_stages[26]._rooms[Mode]._filepath = "rooms/26.The Void_ex.xml"; - super(Stage, Mode); + return super(Stage, Mode); } // Force achievements to be unlockable (expect outside of game mode) diff --git a/repentogon/Patches/XMLData.cpp b/repentogon/Patches/XMLData.cpp index 8fb5a350f..e294dc100 100644 --- a/repentogon/Patches/XMLData.cpp +++ b/repentogon/Patches/XMLData.cpp @@ -30,7 +30,7 @@ using namespace rapidxml; using namespace std; -ANM2* pathCheckanm2 = new ANM2(); +ANM2* pathCheckanm2 = new ANM2; char* bosspoolsxml; //caching this ffs char* fxlayerssxml; //caching this ffs bool itempoolerror = false; @@ -147,6 +147,26 @@ int toint(const string &str) { return 0; } +float tofloat(const string& str) { + if (str.length() > 0) { + char* endPtr; + float returnval = strtof(str.c_str(), &endPtr); + if (endPtr != "\0") { + return returnval; + } + } + return 0.f; +} + +bool tobool(const std::string& str) { + if (str.length() >= 4) { + return str == "true"; + } + return false; +} + +extern tuple GetSetStage(int stageid, bool secondfloor); + XMLAttributes BuildGenericEntry(xml_node* node) { XMLAttributes mod; for (xml_attribute<>* attr = node->first_attribute(); attr; attr = attr->next_attribute()) @@ -328,6 +348,15 @@ void UpdateXMLModEntryData() { } } +void LateXMLAttributeUpdate(XMLDataHolder* toupdate, XMLDataHolder* source, const char* attrname) { + for each (auto node in toupdate->nodes) { + int musicId = toint(node.second[attrname]); + if ((musicId == 0) && (source->byname.find(node.second[attrname]) != source->byname.end())) { + toupdate->nodes[node.first][attrname] = to_string(source->byname[node.second[attrname]]); + } + } +} + //Shameless chatgpt copypaste function string getFileName(const string& filePath) { // Find the position of the last directory separator @@ -471,27 +500,26 @@ HOOK_METHOD(Cutscene, Show, (int cutsceneid)-> void) { //Cutscene XML Hijack - //dirty AI things bool endsWithPNG(const std::string& str) { - if (str.length() >= 4) { - return str.substr(str.length() - 4) == ".png"; - } - return false; + if (str.length() >= 4) { + return str.substr(str.length() - 4) == ".png"; + } + return false; } bool endsWithANM(const std::string& str) { - if (str.length() >= 5) { - return str.substr(str.length() - 5) == ".anm2"; - } - return false; + if (str.length() >= 5) { + return str.substr(str.length() - 5) == ".anm2"; + } + return false; } bool toboolnode(const std::string& str) { - if (str.length() >= 4) { - return str == "true"; - } - return false; + if (str.length() >= 4) { + return str == "true"; + } + return false; } std::bitset<61> changedbackdrops; @@ -520,7 +548,7 @@ HOOK_METHOD(Backdrop, Init, (uint32_t bcktype, bool loadgraphics)-> void) { } } } - + if ((XMLStuff.BackdropData->nodes.count(bcktype) > 0) && (bcktype > 60 || changedbackdrops.test(bcktype))) { // && (bcktype > 0) XMLAttributes node = XMLStuff.BackdropData->nodes[bcktype]; @@ -535,15 +563,15 @@ HOOK_METHOD(Backdrop, Init, (uint32_t bcktype, bool loadgraphics)-> void) { else refbackdrop = 1; } else if (bcktype < 61) refbackdrop = bcktype; - + if (refbackdrop == bcktype) changedbackdrops.reset(refbackdrop); else changedbackdrops.set(refbackdrop); bool isAnm2Gfx = false; - if (refbackdrop == 18 || refbackdrop == 26 || refbackdrop == 35 - || refbackdrop == 52 || refbackdrop == 53 || refbackdrop == 54) + if (refbackdrop == 18 || refbackdrop == 26 || refbackdrop == 35 + || refbackdrop == 52 || refbackdrop == 53 || refbackdrop == 54) isAnm2Gfx = true; - + string gfxpath = node["gfxroot"] + node["gfx"]; bool correctPath = false; if (isAnm2Gfx && (endsWithANM(gfxpath))) { //file path check // added safe-check for anm2 otherwise it would spriteload a png and have a stroke if the param is wrong @@ -562,7 +590,7 @@ HOOK_METHOD(Backdrop, Init, (uint32_t bcktype, bool loadgraphics)-> void) { } pathCheckanm2->Reset(); } - + if (!isAnm2Gfx || correctPath) this->configurations[refbackdrop].gfx = gfxpath; this->configurations[refbackdrop].walls = toint(node["walls"]); @@ -571,7 +599,7 @@ HOOK_METHOD(Backdrop, Init, (uint32_t bcktype, bool loadgraphics)-> void) { this->configurations[refbackdrop].floors = toint(node["floors"]); this->configurations[refbackdrop].floorVariants = toint(node["floorvariants"]); - if ( endsWithPNG(node["lfloorgfx"]) ) { + if (endsWithPNG(node["lfloorgfx"])) { string lFloorGfxpath = node["gfxroot"] + node["lfloorgfx"]; this->configurations[refbackdrop].lFloorGfx = lFloorGfxpath; } @@ -579,14 +607,14 @@ HOOK_METHOD(Backdrop, Init, (uint32_t bcktype, bool loadgraphics)-> void) { this->configurations[refbackdrop].lFloorGfx = ""; } - if ( endsWithPNG(node["nfloorgfx"]) ) { + if (endsWithPNG(node["nfloorgfx"])) { this->configurations[refbackdrop].nFloorGfx = node["gfxroot"] + node["nfloorgfx"]; } else { this->configurations[refbackdrop].nFloorGfx = ""; } - if ( endsWithPNG(node["watergfx"]) ) { + if (endsWithPNG(node["watergfx"])) { this->configurations[refbackdrop].waterGfx = node["gfxroot"] + node["watergfx"]; } else { @@ -597,49 +625,49 @@ HOOK_METHOD(Backdrop, Init, (uint32_t bcktype, bool loadgraphics)-> void) { this->configurations[refbackdrop].reversewatergfx = toboolnode(node["reversewatergfx"]); */ - if ( endsWithANM(node["props"]) ) { + if (endsWithANM(node["props"])) { this->configurations[refbackdrop].props = node["gridgfxroot"] + node["props"]; } else { this->configurations[refbackdrop].props = ""; } - if ( endsWithPNG(node["rocks"]) ) { + if (endsWithPNG(node["rocks"])) { this->configurations[refbackdrop].rocks = node["gridgfxroot"] + node["rocks"]; } else { this->configurations[refbackdrop].rocks = "gfx/grid/rocks_basement.png"; } - if ( endsWithPNG(node["pit"]) ) { + if (endsWithPNG(node["pit"])) { this->configurations[refbackdrop].pit = node["gridgfxroot"] + node["pit"]; } else { this->configurations[refbackdrop].pit = "gfx/grid/grid_pit.png"; } - if ( endsWithPNG(node["waterpit"]) ) { + if (endsWithPNG(node["waterpit"])) { this->configurations[refbackdrop].waterPit = node["gridgfxroot"] + node["waterpit"]; } else { this->configurations[refbackdrop].waterPit = "gfx/grid/grid_pit_water.png"; } - if ( endsWithPNG(node["bridge"]) ) { + if (endsWithPNG(node["bridge"])) { this->configurations[refbackdrop].bridge = node["gridgfxroot"] + node["bridge"]; } else { this->configurations[refbackdrop].bridge = "gfx/grid/grid_bridge.png"; } - if ( endsWithPNG(node["door"]) ) { + if (endsWithPNG(node["door"])) { this->configurations[refbackdrop].door = node["gridgfxroot"] + node["door"]; } else { this->configurations[refbackdrop].door = "gfx/grid/door_01_normaldoor.png"; } - if ( endsWithPNG(node["holeinwall"]) ) { + if (endsWithPNG(node["holeinwall"])) { this->configurations[refbackdrop].holeInWall = node["gridgfxroot"] + node["holeinwall"]; } else { @@ -665,7 +693,6 @@ HOOK_METHOD(Backdrop, Init, (uint32_t bcktype, bool loadgraphics)-> void) { super(bcktype, loadgraphics); } - /* string ogstagespath; int queuedstage = 0; @@ -1625,11 +1652,30 @@ void ProcessXmlNode(xml_node* node,bool force = false) { XMLStuff.StageData->bymod[stage["sourceid"]].push_back(id); //XMLStuff.StageData->byfilepathmulti.tab[currpath].push_back(id); XMLStuff.StageData->byname[stage["name"]] = id; + tuple idx; if (stage.find("consoleid") != stage.end()) { - tuple idx = { toint(stage["consoleid"]), toint(stage["stagealt"]) }; + // this is a custom stage + idx = { toint(stage["consoleid"]) , toint(stage["stagealt"])}; + XMLStuff.StageData->bystagealt[idx] = id; + } + else + { + // this is a base stage + idx = GetSetStage(id, false); XMLStuff.StageData->bystagealt[idx] = id; + ZHL::Log("added first stage id %d to alt %d, %d\n", id, get<0>(idx), get<1>(idx)); + + // handle second floor + tuple idx2 = GetSetStage(id, true); + if (idx2 != idx) { + ZHL::Log("added second stage id %d to alt %d, %d\n", id, get<0>(idx2), get<1>(idx2)); + XMLStuff.StageData->bystagealt[idx2] = id; + } + } - //XMLStuff.StageData->bybasestage[toint(stage["basestage"])] = id; + + if (stage.find("basestage") == stage.end()) + stage["basestage"] = to_string(id); XMLStuff.StageData->nodes[id] = stage; XMLStuff.StageData->byorder[XMLStuff.StageData->nodes.size()] = id; XMLStuff.ModData->stages[stage["sourceid"]] += 1; @@ -2509,6 +2555,9 @@ HOOK_METHOD(Music, LoadConfig, (char* xmlpath, bool ismod)->void) { ProcessModEntry(xmlpath, GetModEntryByContentPath(stringlower(xmlpath))); super(xmlpath, ismod); currpath = ""; + //retroactively patch backdrops into stages (Why here?, load order...yea) + LateXMLAttributeUpdate(XMLStuff.StageData, XMLStuff.BackdropData, "backdrop"); + LateXMLAttributeUpdate(XMLStuff.StageData, XMLStuff.MusicData, "music"); } HOOK_METHOD(SFXManager, LoadConfig, (char* xmlpath, bool ismod)->void) { @@ -3016,10 +3065,10 @@ void CustomXMLCrashPrevention(xml_document* xmldoc, const char* filename) xml_attribute* realid = new xml_attribute(); realid->name("realid"); realid->value(IntToChar(id)); auxnode->append_attribute(realid); node["realid"] = to_string(id); } - int music = toint(node["music"]); - if ((music == 0) && (XMLStuff.MusicData->byname.count(node["music"]) > 0)) { - auxnode->first_attribute("music")->value(IntToChar(XMLStuff.MusicData->byname[node["music"]])); - } + //int music = toint(node["music"]); + //if ((music == 0) && (XMLStuff.MusicData->byname.count(node["music"]) > 0)) { + // auxnode->first_attribute("music")->value(IntToChar(XMLStuff.MusicData->byname[node["music"]])); + //} } } } @@ -3368,6 +3417,7 @@ char * BuildModdedXML(char * xml,const string &filename,bool needsresourcepatch) newid = new xml_attribute(); newid->name("id"); newid->value(IntToChar(maxfxlayerid)); clonedNode->append_attribute(newid); } SingleValXMLParamParse(clonedNode, xmldoc, XMLStuff.BackdropData, "backdrop"); + SingleValXMLParamParse(clonedNode, xmldoc, XMLStuff.StageData, "stage"); root->append_node(clonedNode); } ProcessXmlNode(root,true); @@ -3382,7 +3432,8 @@ char * BuildModdedXML(char * xml,const string &filename,bool needsresourcepatch) xml_attribute* sourceid = new xml_attribute(); sourceid->name("sourceid"); sourceid->value(lastmodid.c_str()); clonedNode->append_attribute(sourceid); inheritdaddy(auxnode, clonedNode); maxfxlayerid += 1; - SingleValXMLParamParse(clonedNode, xmldoc, XMLStuff.BackdropData, "backdrop"); + // implementation of this will be annoying. no backdrop or stage, just _stages_, formatted like "1a,1b" and "1ab" + SingleValXMLParamParse(clonedNode, xmldoc, XMLStuff.StageData, "stages"); root->append_node(clonedNode); } ProcessXmlNode(root, true); @@ -3398,6 +3449,7 @@ char * BuildModdedXML(char * xml,const string &filename,bool needsresourcepatch) inheritdaddy(auxnode, clonedNode); maxfxlayerid += 1; SingleValXMLParamParse(clonedNode, xmldoc, XMLStuff.BackdropData, "backdrop"); + SingleValXMLParamParse(clonedNode, xmldoc, XMLStuff.StageData, "stage"); root->append_node(clonedNode); } ProcessXmlNode(root, true); @@ -3441,10 +3493,10 @@ char * BuildModdedXML(char * xml,const string &filename,bool needsresourcepatch) int id = toint(node["id"]); int music = toint(node["music"]); int backdrop = toint(node["backdrop"]); - if ((music == 0) && (node["music"].length() > 0)) { - char* track = IntToChar(XMLStuff.MusicData->byname[node["music"]]); - auxnode->first_attribute("music")->value(track); - } + //if ((music == 0) && (node["music"].length() > 0)) { + //char* track = IntToChar(XMLStuff.MusicData->byname[node["music"]]); + //auxnode->first_attribute("music")->value(track); + //} xml_node* clonedNode = xmldoc->clone_node(auxnode); xml_attribute* sourceid = new xml_attribute(); sourceid->name("sourceid"); sourceid->value(lastmodid.c_str()); clonedNode->append_attribute(sourceid); inheritdaddy(auxnode, clonedNode); diff --git a/repentogon/Patches/XMLData.h b/repentogon/Patches/XMLData.h index 1d59998f9..be0e9fcaa 100644 --- a/repentogon/Patches/XMLData.h +++ b/repentogon/Patches/XMLData.h @@ -14,6 +14,10 @@ #include "rapidxml.hpp" using namespace std; +extern int toint(const string& str); +extern float tofloat(const string& str); +extern bool tobool(const string& str); + /* Modifiers for character stats, allow to override the computation of Eden's stats, * which we use in order to define stats for modded characters. */ @@ -144,13 +148,13 @@ class XMLDataHolder { } } - XMLAttributes GetNodeById(int name) { + XMLAttributes GetNodeById(int name) { auto iter = this->nodes.find(name); if (iter == this->nodes.end()) { return XMLAttributes(); } else { return iter->second; } } - XMLAttributes GetNodeByOrder(int name) { + XMLAttributes GetNodeByOrder(int name) { auto iter = this->byorder.find(name); if (iter == this->byorder.end()) { return XMLAttributes(); } else { return this->GetNodeById(iter->second); } @@ -497,6 +501,11 @@ class XMLPlayer : public XMLDataHolder { class XMLBackdrop : public XMLDataHolder { public: + // overriden id, overridee id + std::pair backdropState; + // fine + std::string overrideName; + XMLRelEnt relfxlayers; //> XMLRelEnt relfxrays; //> XMLRelEnt relfxparams; //> diff --git a/repentogon/REPENTOGONFileMap.cpp b/repentogon/REPENTOGONFileMap.cpp index c088e276c..6b85c1df2 100644 --- a/repentogon/REPENTOGONFileMap.cpp +++ b/repentogon/REPENTOGONFileMap.cpp @@ -14,6 +14,8 @@ namespace REPENTOGONFileMap { std::vector _stringByFType = { L"resources", L"resources-dlc3", + L"content", + L"content-dlc3", // preemptively pushing bertran back into its grave L"", }; std::wstring _modsPath = L""; @@ -21,7 +23,7 @@ namespace REPENTOGONFileMap { fs::path moddir; std::string modname; -// std::locale utf8loc = std::locale(".UTF-8"); + // std::locale utf8loc = std::locale(".UTF-8"); void ProduceString(std::wstring* part_path, FileMapEntry* entry, std::wstring* outstring) { std::string& moddir = g_Manager->GetModManager()->_mods[entry->ModFolder]->_directory; @@ -63,7 +65,7 @@ namespace REPENTOGONFileMap { elemstoskip.resize(0); size_t i = 0; size_t maxi = 0; - while (std::getline(normalize_stringstream, tokenholder,L'/')) { + while (std::getline(normalize_stringstream, tokenholder, L'/')) { if (tokenholder == L"..") { elemstoskip.push_back(i - 1); elemstoskip.push_back(i); @@ -81,13 +83,13 @@ namespace REPENTOGONFileMap { return false; //trespassed out the resources folder }; while (std::getline(normalize_stringstream, tokenholder, L'/')) { - if ( std::find(elemstoskip.begin(), elemstoskip.end(), i)!=elemstoskip.end() ) { + if (std::find(elemstoskip.begin(), elemstoskip.end(), i) != elemstoskip.end()) { i++; continue; }; wpathbuf += tokenholder; i++; - if (i != maxi && tokenholder!=L"") { + if (i != maxi && tokenholder != L"") { wpathbuf += L'/'; }; }; @@ -102,9 +104,9 @@ namespace REPENTOGONFileMap { entry.ModFolder = modid; wpathbuf.resize(0); format_path(input, wpathbuf); -// NormalizePathString(pathbuf); - //std::transform(wpathbuf.begin(), wpathbuf.end(), wpathbuf.begin(), - // [](wchar_t c) { return std::tolower(c,utf8loc); }); + // NormalizePathString(pathbuf); + //std::transform(wpathbuf.begin(), wpathbuf.end(), wpathbuf.begin(), + // [](wchar_t c) { return std::tolower(c,utf8loc); }); wpathbuf.resize(CharLowerBuffW(wpathbuf.data(), wpathbuf.size())); size_t hashentry = std::hash{}(wpathbuf); _filemap[hashentry] = entry; @@ -121,95 +123,98 @@ namespace REPENTOGONFileMap { }; }; void GenerateMap() { + auto basepath = fs::current_path() / L"mods"; + _modsPath = basepath.wstring(); if (map_init || !repentogonOptions.fileMap) { return; }; map_init = true; - auto basepath = fs::current_path() / L"mods"; - _modsPath = basepath.wstring(); auto start_time = std::chrono::high_resolution_clock::now(); if (g_Manager && g_Manager->GetModManager()) { ModManager* modmngr = g_Manager->GetModManager(); fs::path basemodpath; // for (size_t i = 0; i < modmngr->_mods.size(); i++) { - for (int i = modmngr->_mods.size() - 1; i >= 0;i--) { //inverse order + for (int i = modmngr->_mods.size() - 1; i >= 0; i--) { //inverse order ModEntry* modentry = modmngr->_mods[i]; if (modentry && modentry->_loaded) { basemodpath = basepath / (modentry->_directory); if (fs::is_directory(basemodpath)) { - for (size_t modfoldertype = FolderType::RESOURCES; modfoldertype < FolderType::LAST; modfoldertype++) { + for (size_t modfoldertype = FolderType::RESOURCES; modfoldertype < FolderType::CONTENT; modfoldertype++) { const std::wstring& subdirname = (_stringByFType[modfoldertype]); moddir = basemodpath / subdirname; if (fs::is_directory(moddir)) { - FindFiles(moddir, (FolderType)modfoldertype, i); + fs::path disableFile = moddir / "disable.it"; + if (!fs::exists(disableFile)) { + FindFiles(moddir, (FolderType)modfoldertype, i); + }; }; }; }; }; }; + auto finish_time = std::chrono::high_resolution_clock::now(); + auto diff = std::chrono::duration_cast(finish_time - start_time).count() / 1000.0f; + ZHL::Log("[REPENTOGON] Generated file map in %lf seconds\n", diff); }; - auto finish_time = std::chrono::high_resolution_clock::now(); - auto diff = std::chrono::duration_cast(finish_time - start_time).count() / 1000.0f; - ZHL::Log("[REPENTOGON] Generated file map in %lf seconds\n", diff); - }; -} + } -//HOOK_METHOD(KAGE_Filesys_FileManager, GetExpandedPath, (char* path)->char*) { -// char* out = super(path); -// // printf("[FileMan::GetExpandedPath] Input is %s\nOut is %s\n",path,out); -// return out; -//}; -//HOOK_METHOD(ModManager, LoadConfigs, (void)->void) { -// super(); -// return; -//}; -HOOK_METHOD(ModManager, ListMods, (void)->void) { - super(); - REPENTOGONFileMap::GenerateMap(); -}; + //HOOK_METHOD(KAGE_Filesys_FileManager, GetExpandedPath, (char* path)->char*) { + // char* out = super(path); + // // printf("[FileMan::GetExpandedPath] Input is %s\nOut is %s\n",path,out); + // return out; + //}; + //HOOK_METHOD(ModManager, LoadConfigs, (void)->void) { + // super(); + // return; + //}; + HOOK_METHOD(ModManager, ListMods, (void)->void) { + super(); + REPENTOGONFileMap::GenerateMap(); + }; -std::wstring tempwidestr; -HOOK_METHOD(ModManager, TryRedirectPath, (std_string* param_1, std_string* param_2)->void) { - if (!repentogonOptions.fileMap) { - return super(param_1, param_2); - }; - std::string input = *param_2; -// tempwidestr.resize(input.size() * 4); - int wstr_size=MultiByteToWideChar(CP_UTF8,0,input.data(),input.size(),tempwidestr.data(),0 ); - tempwidestr.resize(wstr_size); - MultiByteToWideChar(CP_UTF8, 0, input.data(), input.size(), tempwidestr.data(), wstr_size); - for (wchar_t& c : tempwidestr) { - if (c == L'\\') { - c = L'/'; + std::wstring tempwidestr; + HOOK_METHOD(ModManager, TryRedirectPath, (std_string* param_1, std_string* param_2)->void) { + if (!repentogonOptions.fileMap) { + return super(param_1, param_2); }; - }; - bool success=REPENTOGONFileMap::NormalizePathString(tempwidestr); - if (!success) { - return super(param_1,param_2); - }; - //std::transform(tempwidestr.begin(), tempwidestr.end(), tempwidestr.begin(), - // [](wchar_t c) { return std::tolower(c, REPENTOGONFileMap::utf8loc); }); - tempwidestr.resize(CharLowerBuffW(tempwidestr.data(), tempwidestr.size())); - size_t hashentry = std::hash{}(tempwidestr); - REPENTOGONFileMap::FileMapEntry* mapentry = REPENTOGONFileMap::GetEntry(hashentry); - if (mapentry) { - REPENTOGONFileMap::outputholder.resize(0); -// REPENTOGONFileMap::outputholder.reserve(260); -// param_1->reserve(260); - ProduceString(&tempwidestr, mapentry, &(REPENTOGONFileMap::outputholder)); - if (!fs::exists(REPENTOGONFileMap::outputholder)) { - ZHL::Log("[REPENTOGON] File %s doesn't exist in a mod %s, hash mismatch?\n",param_2->c_str(), g_Manager->GetModManager()->_mods[mapentry->ModFolder]->_name.c_str()); + std::string input = *param_2; + // tempwidestr.resize(input.size() * 4); + int wstr_size = MultiByteToWideChar(CP_UTF8, 0, input.data(), input.size(), tempwidestr.data(), 0); + tempwidestr.resize(wstr_size); + MultiByteToWideChar(CP_UTF8, 0, input.data(), input.size(), tempwidestr.data(), wstr_size); + for (wchar_t& c : tempwidestr) { + if (c == L'\\') { + c = L'/'; + }; + }; + bool success = REPENTOGONFileMap::NormalizePathString(tempwidestr); + if (!success) { return super(param_1, param_2); }; - new (param_1) std::string(""); - int out_str_size=WideCharToMultiByte(CP_UTF8,0,REPENTOGONFileMap::outputholder.data(),REPENTOGONFileMap::outputholder.size(),param_1->data(),0,0,0); - param_1->resize(out_str_size); - WideCharToMultiByte(CP_UTF8, 0, REPENTOGONFileMap::outputholder.data(), REPENTOGONFileMap::outputholder.size(), param_1->data(), out_str_size, 0, 0); - return; - } - else { - new (param_1) std::string(*param_2); //thats what the game does to a string anyways so no need to do super() - return; + //std::transform(tempwidestr.begin(), tempwidestr.end(), tempwidestr.begin(), + // [](wchar_t c) { return std::tolower(c, REPENTOGONFileMap::utf8loc); }); + tempwidestr.resize(CharLowerBuffW(tempwidestr.data(), tempwidestr.size())); + size_t hashentry = std::hash{}(tempwidestr); + REPENTOGONFileMap::FileMapEntry* mapentry = REPENTOGONFileMap::GetEntry(hashentry); + if (mapentry) { + REPENTOGONFileMap::outputholder.resize(0); + // REPENTOGONFileMap::outputholder.reserve(260); + // param_1->reserve(260); + ProduceString(&tempwidestr, mapentry, &(REPENTOGONFileMap::outputholder)); + if (!fs::exists(REPENTOGONFileMap::outputholder)) { + ZHL::Log("[REPENTOGON] File %s doesn't exist in a mod %s, hash mismatch?\n", param_2->c_str(), g_Manager->GetModManager()->_mods[mapentry->ModFolder]->_name.c_str()); + return super(param_1, param_2); + }; + new (param_1) std::string(""); + int out_str_size = WideCharToMultiByte(CP_UTF8, 0, REPENTOGONFileMap::outputholder.data(), REPENTOGONFileMap::outputholder.size(), param_1->data(), 0, 0, 0); + param_1->resize(out_str_size); + WideCharToMultiByte(CP_UTF8, 0, REPENTOGONFileMap::outputholder.data(), REPENTOGONFileMap::outputholder.size(), param_1->data(), out_str_size, 0, 0); + return; + } + else { + new (param_1) std::string(*param_2); //thats what the game does to a string anyways so no need to do super() + return; + }; }; -}; +} \ No newline at end of file diff --git a/repentogon/REPENTOGONFileMap.h b/repentogon/REPENTOGONFileMap.h index ba5a45c5b..2bc4bad39 100644 --- a/repentogon/REPENTOGONFileMap.h +++ b/repentogon/REPENTOGONFileMap.h @@ -8,6 +8,8 @@ namespace REPENTOGONFileMap { enum FolderType { RESOURCES, RESOURCES_DLC3, + CONTENT, + CONTENT_DLC3, LAST }; struct FileMapEntry { diff --git a/updater/updater.rc b/updater/updater.rc new file mode 100644 index 000000000..f460a2935 --- /dev/null +++ b/updater/updater.rc @@ -0,0 +1,69 @@ +// Microsoft Visual C++ generated resource script. +// +#include "updater/updater_resources.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Français (France) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_FRA) +LANGUAGE LANG_FRENCH, SUBLANG_FRENCH +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "updater/updater_resources.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Resource data +// + +IDB_EMBEDEXE1 RCDATA "C:/Users/nami/Documents/GitHub/REPENTOGON/updater_rsrc" + +#endif // Français (France) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/updater_rsrc b/updater_rsrc new file mode 100644 index 000000000..ce23c6548 Binary files /dev/null and b/updater_rsrc differ