mirror of
https://github.com/ApfelTeeSaft/expresso.git
synced 2026-08-26 19:23:27 +00:00
268 lines
10 KiB
C++
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, ¤t, 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
|