Skip to content

Commit 4c4cdb6

Browse files
committed
IntegrityCheckBypass (RE9): Fix heartbeat cluster getting found late
1 parent 5a75330 commit 4c4cdb6

1 file changed

Lines changed: 31 additions & 10 deletions

File tree

src/mods/IntegrityCheckBypass.cpp

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2137,7 +2137,7 @@ void IntegrityCheckBypass::re9_heartbeat_bypass() {
21372137
static std::vector<uintptr_t> candidates{};
21382138
static uint32_t last_scan_frame = 0;
21392139
static int confirmation_count = 0;
2140-
static constexpr int CONFIRMATIONS_NEEDED = 5;
2140+
static constexpr int CONFIRMATIONS_NEEDED = 3;
21412141
static constexpr int32_t MAX_DISTANCE = 1000;
21422142
static constexpr size_t HEARTBEAT_COUNT = 6;
21432143

@@ -2150,29 +2150,50 @@ void IntegrityCheckBypass::re9_heartbeat_bypass() {
21502150
heartbeat_offset_start[i] = frame_count;
21512151
}
21522152
} else if (frame_count > 100 && frame_count != last_scan_frame) {
2153+
// Debug for RE9 (known to be at 0x3328)
2154+
#if 0
2155+
for (size_t i = 0; i < HEARTBEAT_COUNT; i++) {
2156+
auto val = *(uint32_t*)(renderer_addr + 0x3328 + i * 4);
2157+
spdlog::info("[IntegrityCheckBypass] Heartbeat candidate {}: {} (diff: {}), actual: {}", i, val, frame_count - val, frame_count);
2158+
}
2159+
#endif
2160+
21532161
last_scan_frame = frame_count;
21542162

2155-
// Scan renderer struct for runs of 6 consecutive DWORDs all within
2156-
// MAX_DISTANCE of frame_count (and <= frame_count).
2163+
// Two detection modes for the heartbeat cluster:
2164+
// Normal: sentinel(1), 6 valid heartbeats, sentinel(0)
2165+
// Early: sentinel(1), 0 (heartbeat not yet written), 5 valid, sentinel(0)
2166+
// In RE9, heartbeat[0] doesn't get its first write until ~frame 930.
2167+
// The anti-tamper starts corrupting job pointers well before that.
21572168
std::vector<uintptr_t> this_frame{};
21582169
for (size_t i = 0x2000; i + HEARTBEAT_COUNT * 4 <= 0x4000; i += sizeof(uint32_t)) {
21592170
try {
21602171
auto* ints = reinterpret_cast<uint32_t*>(renderer_addr + i);
21612172
if (ints[-1] != 1) {
2162-
continue; // the DWORD immediately preceding the 6 we care about should be 1, it's used as a sentinel for the start of the heartbeat cluster.
2173+
continue; // sentinel before cluster must be 1
21632174
}
21642175
if (ints[HEARTBEAT_COUNT] != 0) {
2165-
continue; // the DWORD immediately following the 6 we care about should be 0, it's used as a sentinel for the end of the heartbeat cluster.
2176+
continue; // sentinel after cluster must be 0
21662177
}
2167-
bool ok = true;
2178+
2179+
// Check if all 6 are valid heartbeats
2180+
bool all_valid = true;
2181+
// Check if ints[0] == 0 (unwritten) and ints[1..5] are valid
2182+
bool early_detect = (ints[0] == 0);
2183+
21682184
for (size_t j = 0; j < HEARTBEAT_COUNT; j++) {
21692185
auto val = ints[j];
2170-
if (val < 100 || val > frame_count || (frame_count - val) >= MAX_DISTANCE) {
2171-
ok = false;
2172-
break;
2186+
bool in_range = val > 0 && val <= frame_count && (frame_count - val) < (uint32_t)MAX_DISTANCE;
2187+
if (!in_range) {
2188+
all_valid = false;
2189+
}
2190+
// For early detect: ints[1..5] must all be in range
2191+
if (j > 0 && !in_range) {
2192+
early_detect = false;
21732193
}
21742194
}
2175-
if (ok) {
2195+
2196+
if (all_valid || early_detect) {
21762197
this_frame.push_back(renderer_addr + i);
21772198
}
21782199
} catch (...) {}

0 commit comments

Comments
 (0)