@@ -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