Files
expresso/identity_spoof.cpp
T
2026-08-11 10:19:16 +02:00

268 lines
10 KiB
C++

// lol id spoof
#include "expresso_recovered.h"
#include <cstring>
namespace expresso {
// The five APlayerState slots, in the order they are pushed at
// RVA 0x167B3C..0x167B5C
const uint32_t kSteamIdCandidateSlots[5] = {0x3C0, 0x3C8, 0x3D0, 0x3B8, 0x3B0};
// RVA 0x167ABF..0x167BE9 find the real SteamID64
uint64_t LocateOriginalSteamId(const IMemory& mem, uint64_t game_base,
LocalPlayerCache& cache, uint64_t now_ms) {
const uint64_t pc = GetLocalPlayerController(mem, game_base, cache, now_ms);
if (pc == 0) return 0; // 0x167B10
const uint64_t player_state = SafeReadPtr(mem, pc + world::kPlayerState);
if (!IsCanonicalUserPointer(player_state)) return 0; // 0x167B2D
for (size_t i = 0; i < kSteamIdCandidateSlotCount; ++i) {
const uint64_t slot = player_state + kSteamIdCandidateSlots[i];
// the slot is a pointer to an object holding the ID
const uint64_t pointee = SafeReadPtr(mem, slot); // 0x167B78
if (pointee > 0x10000) { // 0x167B80
for (uint32_t off = 0; off < kSteamIdProbeSpan; off += 8) { // 0x167BAD
const uint64_t v = SafeReadPtr(mem, pointee + off); // 0x167B95
if (steam::IsPlausibleId64(v)) return v; // 0x167BA1
}
}
// the slot holds the ID inline
const uint64_t direct = SafeReadPtr(mem, slot); // 0x167BB6
if (steam::IsPlausibleId64(direct)) return direct; // 0x167BC1
}
return 0; // 0x167CB1
}
// RVA 0x166B21..0x166B48 which regions get scanned
namespace mem_flags {
constexpr uint32_t kMemCommit = 0x00001000;
constexpr uint32_t kMemImage = 0x01000000;
constexpr uint32_t kPageReadWrite = 0x00000004;
constexpr uint32_t kPageExecReadWrite= 0x00000040;
constexpr uint32_t kPageGuard = 0x00000100;
constexpr uint32_t kWritableMask = kPageReadWrite | kPageExecReadWrite;
} // namespace mem_flags
bool IsScannableRegion(const MemoryRegion& region) {
if (region.state != mem_flags::kMemCommit) return false; // 0x166B21
if (region.type == mem_flags::kMemImage) return false; // 0x166B2A
if ((region.protect & mem_flags::kWritableMask) == 0)
return false; // 0x166B36
if (region.protect & mem_flags::kPageGuard) return false; // 0x166B3A
if (region.size < 8) return false; // 0x166B44
return true;
}
// RVA 0x166690 the matcher
size_t ScanRegionForQword(const IMemory& mem, uint64_t base, uint64_t size,
uint64_t needle, uint64_t* out, size_t out_capacity) {
if (out == nullptr || out_capacity == 0 || size < 8) return 0;
const size_t cap = (out_capacity < kMaxHitsPerChunk) ? out_capacity
: kMaxHitsPerChunk;
size_t hits = 0;
const uint64_t end = base + size - 7; // 0x1666A6
for (uint64_t p = base; p < end && hits < cap; p += 8) {
uint64_t v = 0;
if (!mem.Read(p, &v, sizeof(v))) continue;
if (v == needle) out[hits++] = p; // 0x1666BF
}
return hits;
}
// RVA 0x1665E0 one guarded write
bool ReplaceSteamIdAt(IMemory& mem, uint64_t addr, uint64_t new_id) {
if (!IsCanonicalUserPointer(addr)) return false; // 0x166573
uint64_t current = 0;
if (!mem.Read(addr, &current, sizeof(current))) return false; // 0x166625
if (!steam::IsPlausibleId64(current)) return false; // 0x166642
return mem.Write(addr, &new_id, sizeof(new_id)); // 0x166659
}
// RVA 0x167260 apply across every located slot
size_t ReplaceSteamId(IMemory& mem, uint64_t* slots, size_t& slot_count,
uint64_t new_id) {
if (slots == nullptr) return 0;
size_t written = 0;
for (size_t i = 0; i < slot_count; ++i) {
if (ReplaceSteamIdAt(mem, slots[i], new_id)) { // 0x16734E
slots[written++] = slots[i]; // 0x16736B compaction
}
}
slot_count = written; // 0x16745B
return written;
}
// RVA 0x167E70 the ProcessEvent blocklist
const char* const kBlockedIdentityFunctions[11] = {
"CreateSteamIDFromString", // .rdata 0x59AA20, inserted at 0x167F07
"GetUniqueNetIDFromPlayerState", // .rdata 0x59AA38 0x167F30
"SteamID64", // .rdata 0x59AA58 0x167F58
"GetLoginStatus", // 0x167F7F
"GetUserAccount", // 0x167FA5
"GetUserPrivilege", // 0x167FCB
"bAntiCheatProtected", // 0x167FF1
"GetUserID", // 0x168017
"LoginUser", // 0x16803D
"OnPlayerLoginChanged", // 0x168063
"HasMatchmakingBan", // 0x16808C
};
// RVA 0x168183: movabs r9, 0xCBF29CE484222325
// RVA 0x168192: movabs r8, 0x100000001B3
// RVA 0x1681A0: movzx eax,[rcx+rbx]; xor r9,rax; imul r9,r8
uint64_t Fnv1a64(const char* s) {
uint64_t h = 0xCBF29CE484222325ULL;
if (s == nullptr) return h;
for (const unsigned char* p = reinterpret_cast<const unsigned char*>(s);
*p != 0; ++p) {
h ^= static_cast<uint64_t>(*p);
h *= 0x100000001B3ULL;
}
return h;
}
bool IsBlockedIdentityFunction(const char* name) {
if (name == nullptr) return false;
const uint64_t h = Fnv1a64(name);
for (size_t i = 0; i < kBlockedIdentityFunctionCount; ++i) {
if (h == Fnv1a64(kBlockedIdentityFunctions[i]) &&
std::strcmp(name, kBlockedIdentityFunctions[i]) == 0) {
return true;
}
}
return false;
}
// RVA 0x167610 the event observer
IdentityEvent ClassifyIdentityEvent(const char* function_name) {
if (function_name == nullptr) return IdentityEvent::kOther;
if (std::strstr(function_name, "ClientReservationPending")) // 0x1676DB
return IdentityEvent::kReservationPending;
if (std::strstr(function_name, "OnRep_ReservationAccepted")) // 0x16772E
return IdentityEvent::kReservationAccepted;
if (std::strstr(function_name, "ClientTravelInternal")) // 0x1677C3
return IdentityEvent::kClientTravelInternal;
if (std::strstr(function_name, "ClientAckTraveling")) // 0x167885
return IdentityEvent::kClientAckTraveling;
return IdentityEvent::kOther;
}
void OnIdentityEvent(IdentitySpoofState& state, IdentityEvent event,
uint64_t now_ms) {
if (!state.enabled) return; // 0x167633
switch (event) {
case IdentityEvent::kReservationPending:
// The client has asked the session for a slot. Mark it in flight and
// stop any scan, so nothing races the handshake (0x1676FB/0x167706)
state.reservation_pending = true;
state.scan_in_progress = false;
break;
case IdentityEvent::kReservationAccepted:
// The session said yes. Arm the swap for 1700 ms from now, late
// enough that the reservation is settled, early enough to be in place
// before the server reads the identity again. (0x16774E..0x167797)
if (!state.reservation_pending && // 0x167755
state.candidate_count > 0 && // 0x167759
!state.applied && // 0x167762
!state.scan_in_progress && // 0x16776B
!state.restore_armed) { // 0x167776
state.apply_deadline_ms = now_ms + kApplyDelayMs; // 0x167787
state.apply_armed = true; // 0x167797
}
break;
case IdentityEvent::kClientTravelInternal: // 0x1677E7
case IdentityEvent::kClientAckTraveling: // 0x1678A5
// Travel is under way: the identity has served its purpose. Disarm any
// pending apply and schedule the real ID to go back 800 ms from now
state.apply_armed = false;
if (!state.scan_in_progress && state.applied && !state.restore_armed) {
state.scan_in_progress = false;
state.restore_armed = true; // 0x167825
state.restore_deadline_ms = now_ms + kRestoreDelayMs; // 0x167831
}
state.reservation_pending = false; // 0x167859
break;
case IdentityEvent::kOther:
break;
}
}
// RVA 0x167950 the per-frame tick, as a decision function
IdentityTickAction IdentitySpoofTick(IdentitySpoofState& state,
uint64_t now_ms) {
// deferred apply, armed by the observer (0x16798C)
if (state.apply_armed && now_ms >= state.apply_deadline_ms) { // 0x1679A4
state.apply_armed = false; // 0x1679AB
if (!state.scan_in_progress && !state.restore_armed) { // 0x1679B1/BC
state.applied = true; // 0x1679D7
state.applied_at_ms = now_ms; // 0x1679EB
return IdentityTickAction::kApplyFake;
}
}
// the toggle went off: undo everything (0x167A13)
if (!state.enabled) {
if (!state.was_enabled) return IdentityTickAction::kNothing; // 0x167A18
state.was_enabled = false; // 0x167A82
if (state.applied && state.original_id != 0) { // 0x167A25/2E
state.applied = false;
state.applied_at_ms = 0;
state.reservation_pending = false;
state.scan_in_progress = false;
state.apply_armed = false;
return IdentityTickAction::kRestoreOriginal;
}
return IdentityTickAction::kNothing;
}
state.was_enabled = true; // 0x167A8E
// no real ID yet: locate it, with a 119-tick retry back-off
if (state.original_id == 0) { // 0x167A95
if (state.retry_cooldown > 0) { // 0x167AA2
--state.retry_cooldown; // 0x167CDE
return IdentityTickAction::kNothing;
}
if (state.scan_complete) return IdentityTickAction::kNothing; // 0x167AB0
return IdentityTickAction::kLocateOriginal;
}
// deferred restore, armed on travel (0x167D1B)
if (state.restore_armed && now_ms >= state.restore_deadline_ms) { // 0x167D33
state.restore_armed = false; // 0x167D3A
if (state.applied) { // 0x167D40
state.applied = false; // 0x167D5E
state.applied_at_ms = 0;
state.scan_in_progress = false;
state.apply_armed = false;
state.enabled = false; // 0x167D81
return IdentityTickAction::kRestoreOriginal;
}
}
// --- hard auto-revert 10 s after the swap (0x167DA0)
if (state.applied && state.applied_at_ms != 0 && !state.restore_armed &&
now_ms - state.applied_at_ms > kAutoRevertMs) { // 0x167DD4
state.applied = false; // 0x167DED
state.applied_at_ms = 0;
state.reservation_pending = false;
state.scan_in_progress = false;
state.apply_armed = false;
state.enabled = false; // 0x167E16
return IdentityTickAction::kRestoreOriginal;
}
return IdentityTickAction::kNothing;
}
} // namespace expresso