Skip to content

Commit 6b23995

Browse files
committed
ESSM: Fix problems caused by mausoleum mom's heart death.
1 parent d4fd471 commit 6b23995

File tree

2 files changed

+154
-19
lines changed

2 files changed

+154
-19
lines changed

repentogon/SaveStateManagement/EntitySaveStateManagement.cpp

Lines changed: 153 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,64 @@ namespace {
9595
};
9696

9797
#if DEBUG_MSG
98+
static size_t s_debugContextLevel = 0;
99+
static constexpr char DEBUG_INDENT[] = " ";
100+
static std::string s_debugIndent = "";
101+
98102
static void LogDebug(const char* format, ...)
99103
{
100104
va_list args;
101105
va_start(args, format);
102106
std::string message = REPENTOGON::StringFormat(format, args);
103107
va_end(args);
104108

109+
message = LOG_DEBUG_HEADER + s_debugIndent + message;
105110
KAGE::_LogMessage(0, message.c_str());
106111
}
112+
113+
static void BeginDebugContext(const char* message)
114+
{
115+
LogDebug("Start %s\n", message);
116+
s_debugContextLevel++;
117+
s_debugIndent += DEBUG_INDENT;
118+
}
119+
120+
static void EndDebugContext(const char* message)
121+
{
122+
s_debugContextLevel--;
123+
s_debugIndent.resize(s_debugIndent.size() - (sizeof(DEBUG_INDENT) - 1));
124+
LogDebug("End %s\n", message);
125+
}
126+
127+
namespace {
128+
struct ScopedDebugContext
129+
{
130+
private:
131+
const char* m_message;
132+
133+
public:
134+
ScopedDebugContext(const char* message)
135+
: m_message(message)
136+
{
137+
BeginDebugContext(message);
138+
}
139+
140+
~ScopedDebugContext()
141+
{
142+
EndDebugContext(m_message);
143+
}
144+
};
145+
}
146+
107147
#else
148+
namespace
149+
{
150+
struct ScopedDebugContext
151+
{
152+
ScopedDebugContext(const char* message) {}
153+
};
154+
}
155+
108156
static void LogDebug(const char* format, ...) {};
109157
#endif
110158

@@ -449,21 +497,21 @@ namespace ESSM::IdManager
449497
{
450498
uint32_t id = s_systemData.reusableIds.back();
451499
s_systemData.reusableIds.pop_back();
452-
LogDebug(LOG_DEBUG_HEADER "New ID: %u\n", id);
500+
LogDebug("New ID: %u\n", id);
453501
return id;
454502
}
455503

456504
uint32_t id = s_systemData.totalIds;
457505
s_systemData.totalIds++;
458506
s_systemData.hijackedStates.emplace_back();
459-
LogDebug(LOG_DEBUG_HEADER "New ID: %u\n", id);
507+
LogDebug("New ID: %u\n", id);
460508
return id;
461509
}
462510

463511
static void ClearId(uint32_t id)
464512
{
465513
s_systemData.reusableIds.push_back(id);
466-
LogDebug(LOG_DEBUG_HEADER "Cleared ID: %u\n", id);
514+
LogDebug("Cleared ID: %u\n", id);
467515
}
468516
}
469517

@@ -480,6 +528,7 @@ namespace ESSM
480528
static_assert(std::is_integral_v<Marker> && sizeof(Marker) >= 2, "Chosen MarkerType cannot be used.");
481529

482530
private:
531+
static const Marker DEBUG_CLEARED_MARKER = 0x5250; // used to identify incorrect clears more reliably in debug
483532
static const Marker CLEARED_MARKER = 0x5249;
484533
static const Marker READ_MARKER = 0x5248;
485534
static const Marker HIJACK_MARKER = 0x5247;
@@ -516,6 +565,15 @@ namespace ESSM
516565
return IsHijacked(data) || IsWritten(data) || IsRead(data) || IsCleared(data);
517566
}
518567

568+
#ifndef NDEBUG
569+
static void SetDebugCleared(Data& data)
570+
{
571+
Traits::SetMarker(data, DEBUG_CLEARED_MARKER);
572+
}
573+
#else
574+
static void SetDebugCleared(Data& data) {}
575+
#endif
576+
519577
static void SetHijacked(Data& data)
520578
{
521579
Traits::SetMarker(data, HIJACK_MARKER);
@@ -1011,20 +1069,30 @@ namespace ESSM::Utils
10111069

10121070
namespace ESSM::Core
10131071
{
1014-
static auto ClearState = [](const auto* obj, ClearedIds& clearedIds)
1072+
static auto NewState = [](auto* obj)
1073+
{
1074+
using T = std::remove_cv_t<std::remove_pointer_t<decltype(obj)>>;
1075+
using Traits = ESSM::Traits::TraitsFor<T>;
1076+
using Manager = typename Traits::Hijack;
1077+
1078+
Manager::NewHijack(*obj);
1079+
};
1080+
1081+
static auto ClearState = [](auto* obj, ClearedIds& clearedIds)
10151082
{
10161083
using T = std::remove_cv_t<std::remove_pointer_t<decltype(obj)>>;
10171084
using Traits = ESSM::Traits::TraitsFor<T>;
10181085
using Manager = typename Traits::Hijack;
10191086

10201087
assert(Manager::IsHijacked(*obj));
10211088
uint32_t id = Manager::GetId(*obj);
1089+
Manager::SetDebugCleared(*obj);
10221090
IdManager::ClearId(id);
10231091

10241092
clearedIds.emplace_back(id);
10251093
};
10261094

1027-
static auto ClearState_HijackedOnly = [](const auto* obj, ClearedIds& clearedIds)
1095+
static auto ClearState_HijackedOnly = [](auto* obj, ClearedIds& clearedIds)
10281096
{
10291097
using T = std::remove_cv_t<std::remove_pointer_t<decltype(obj)>>;
10301098
using Traits = ESSM::Traits::TraitsFor<T>;
@@ -1047,7 +1115,7 @@ namespace ESSM::Core
10471115
uint32_t targetId = IdManager::NewId();
10481116
Manager::SetId(*obj, targetId);
10491117
s_systemData.hijackedStates[targetId] = s_systemData.hijackedStates[sourceId];
1050-
LogDebug(LOG_DEBUG_HEADER "Copied Entity %d -> %d\n", sourceId, targetId);
1118+
LogDebug("Copied Entity %d -> %d\n", sourceId, targetId);
10511119

10521120
return std::make_pair(sourceId, targetId);
10531121
};
@@ -1057,6 +1125,14 @@ namespace ESSM::Core
10571125
copiedIds.emplace_back(CopyState_NoBatch(obj));
10581126
};
10591127

1128+
static void NewStates(CollectedStates& collectedStates)
1129+
{
1130+
for (auto& entity : collectedStates)
1131+
{
1132+
std::visit([](auto* obj) { NewState(obj); }, entity);
1133+
}
1134+
}
1135+
10601136
static void ClearSaveStates(CollectedStates& collectedSaves)
10611137
{
10621138
ClearedIds clearedIds;
@@ -1109,7 +1185,7 @@ namespace ESSM::Core
11091185
// Public API
11101186
namespace ESSM
11111187
{
1112-
void EntitySaveState_ClearBatch(const std::vector<EntitySaveState>& vector)
1188+
void EntitySaveState_ClearBatch(std::vector<EntitySaveState>& vector)
11131189
{
11141190
ClearedIds clearedIds;
11151191
clearedIds.reserve(vector.size());
@@ -2311,6 +2387,71 @@ HOOK_METHOD(Game, SaveBackwardsStage, (int stage) -> void)
23112387
ESSM::Core::CopySaveStates(collection);
23122388
}
23132389

2390+
HOOK_STATIC(Entity_NPC, moms_heart_mausoleum_death, () -> void, __cdecl)
2391+
{
2392+
constexpr size_t STATE_MAUSOLEUM_HEART_KILLED_FLAG_IDX = 46;
2393+
constexpr size_t STATE_MAUSOLEUM_HEART_KILLED_WORD_OFFSET = offsetof(Game, _gameStateFlags) + (STATE_MAUSOLEUM_HEART_KILLED_FLAG_IDX / 32) * sizeof(uint32_t);
2394+
constexpr uint32_t STATE_MAUSOLEUM_HEART_KILLED_FLAG = 1 << (STATE_MAUSOLEUM_HEART_KILLED_FLAG_IDX % 32);
2395+
2396+
Game* game = g_Game;
2397+
uint32_t bitset = *(uint32_t*)((uintptr_t)game + STATE_MAUSOLEUM_HEART_KILLED_WORD_OFFSET);
2398+
uint32_t deathTriggered = (bitset & STATE_MAUSOLEUM_HEART_KILLED_FLAG) == 0;
2399+
2400+
2401+
if (!deathTriggered)
2402+
{
2403+
return super();
2404+
}
2405+
2406+
ScopedDebugContext context("Mom Heart Mausoleum Death");
2407+
2408+
CollectedStates collection;
2409+
collection.reserve(DEFAULT_COLLECT_RESERVE);
2410+
2411+
RoomDescriptor* rooms = game->_gridRooms;
2412+
const RoomDescriptor& currentRoom = *game->_room->_descriptor;
2413+
2414+
for (size_t i = 0; i < MAX_ROOMS; i++)
2415+
{
2416+
RoomDescriptor& room = rooms[i];
2417+
if (!room.Data || &room == &currentRoom || room.Data->Type == eRoomType::ROOM_ULTRASECRET || room.GridIndex == eGridRooms::ROOM_LIL_PORTAL_IDX)
2418+
{
2419+
continue;
2420+
}
2421+
2422+
std::vector<EntitySaveState>& entityStates = room.SavedEntities;
2423+
for (size_t i = 0; i < entityStates.size(); i++)
2424+
{
2425+
EntitySaveState& entityState = entityStates[i];
2426+
if (entityState.type == eEntityType::ENTITY_PICKUP)
2427+
{
2428+
collection.emplace_back(&entityState);
2429+
}
2430+
}
2431+
}
2432+
2433+
ESSM::Core::ClearSaveStates(collection);
2434+
2435+
super();
2436+
2437+
constexpr uint32_t FLAG_CLEAR = 1 << 0;
2438+
RoomDescriptor& lastBossRoom = game->_gridRooms[game->_lastBossRoomListIdx];
2439+
if (lastBossRoom.Data && (lastBossRoom.Flags & FLAG_CLEAR) != 0)
2440+
{
2441+
// bugs and flies were added to the room
2442+
collection.clear();
2443+
for (auto& entity : lastBossRoom.SavedEntities)
2444+
{
2445+
if (!ESSM::EntityHijackManager::HasMarker(entity))
2446+
{
2447+
collection.emplace_back(&entity);
2448+
}
2449+
}
2450+
2451+
ESSM::Core::NewStates(collection);
2452+
}
2453+
}
2454+
23142455
// Clear backwards stage save state, even though the game simply sets room count to 0, to avoid having to iterate "uninitialized" RoomDescriptors in the BackwardsStage.
23152456
HOOK_METHOD(Game, ResetState, () -> void)
23162457
{
@@ -2324,7 +2465,7 @@ HOOK_METHOD(Game, ResetState, () -> void)
23242465

23252466
HOOK_METHOD(GameState, Clear, () -> void)
23262467
{
2327-
LogDebug(LOG_DEBUG_HEADER "Start GameState::Clear\n");
2468+
ScopedDebugContext context("GameState::Clear");
23282469

23292470
CollectedStates collection;
23302471
collection.reserve(DEFAULT_COLLECT_RESERVE);
@@ -2334,13 +2475,11 @@ HOOK_METHOD(GameState, Clear, () -> void)
23342475

23352476
// player save states are handled by GameStatePlayer::Init
23362477
super();
2337-
2338-
LogDebug(LOG_DEBUG_HEADER "End GameState::Clear\n");
23392478
}
23402479

23412480
HOOK_METHOD(Game, SaveState, (GameState* state) -> void)
23422481
{
2343-
LogDebug(LOG_DEBUG_HEADER "Start Game::SaveState\n");
2482+
ScopedDebugContext context("Game::SaveState");
23442483

23452484
// ASSUMPTION: It is assumed that the state has already been cleared.
23462485
// This is because SaveState always calls GameState::Clear before saving
@@ -2353,13 +2492,11 @@ HOOK_METHOD(Game, SaveState, (GameState* state) -> void)
23532492
ESSM::Core::CopySaveStates(collection);
23542493

23552494
// players are handled by Entity_Player::Init
2356-
2357-
LogDebug(LOG_DEBUG_HEADER "End Game::SaveState\n");
23582495
}
23592496

23602497
HOOK_METHOD(Game, RestoreState, (GameState* state, bool startGame) -> void)
23612498
{
2362-
LogDebug(LOG_DEBUG_HEADER "Start Game::RestoreState\n");
2499+
ScopedDebugContext context("Game::RestoreState");
23632500

23642501
CollectedStates collection;
23652502
collection.reserve(DEFAULT_COLLECT_RESERVE);
@@ -2368,8 +2505,6 @@ HOOK_METHOD(Game, RestoreState, (GameState* state, bool startGame) -> void)
23682505

23692506
super(state, startGame);
23702507
// copy is in a separate patch as copying it here might cause problems due to callbacks running in the mean time.
2371-
2372-
LogDebug(LOG_DEBUG_HEADER "End Game::RestoreState\n");
23732508
}
23742509

23752510
HOOK_METHOD(Level, RestoreGameState, (GameState* state) -> void)
@@ -2873,7 +3008,7 @@ static void __fastcall asm_clear_smart_pointer(EntitySaveState* saveState)
28733008
CollectedStates collection = {saveState};
28743009
ESSM::Core::ClearSaveStates(collection);
28753010

2876-
LogDebug(LOG_DEBUG_HEADER "Smart pointer Cleared: %u\n", id);
3011+
LogDebug("Smart pointer Cleared: %u\n", id);
28773012
}
28783013

28793014
saveState->destructor();
@@ -2893,7 +3028,7 @@ static void Patch_ReferenceCount_EntitySaveStateDestructor()
28933028
static void __stdcall asm_hijack_new_flip_state(EntitySaveState& saveState)
28943029
{
28953030
uint32_t id = ESSM::EntityHijackManager::NewHijack(saveState);
2896-
LogDebug(LOG_DEBUG_HEADER "New Flip State: %u\n", id);
3031+
LogDebug("New Flip State: %u\n", id);
28973032
}
28983033

28993034
static void Patch_PickupInitFlipState_CreateSaveState()

repentogon/SaveStateManagement/EntitySaveStateManagement.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace EntitySaveStateManagement
1515
uint32_t& FamiliarData_GetState(FamiliarData& data);
1616
uint32_t& FamiliarData_GetRoomClearCount(FamiliarData& data);
1717

18-
void EntitySaveState_ClearBatch(const std::vector<EntitySaveState>& vector);
18+
void EntitySaveState_ClearBatch(std::vector<EntitySaveState>& vector);
1919

2020
void RegisterSaveState(EntitySaveState& saveState);
2121
}

0 commit comments

Comments
 (0)