import ctypes
from ctypes import wintypes
import struct

PROCESS_VM_READ = 0x0010
PROCESS_QUERY_INFORMATION = 0x0400

kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
OpenProcess = kernel32.OpenProcess
OpenProcess.restype = wintypes.HANDLE
OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]

ReadProcessMemory = kernel32.ReadProcessMemory
ReadProcessMemory.restype = wintypes.BOOL
ReadProcessMemory.argtypes = [wintypes.HANDLE, wintypes.LPCVOID, wintypes.LPVOID, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)]

CloseHandle = kernel32.CloseHandle
CloseHandle.restype = wintypes.BOOL
CloseHandle.argtypes = [wintypes.HANDLE]

pid = 39484
h_proc = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, False, pid)

base = 0x7FF6AFC20000
text_start = base + 0x1000
text_size = 0x2E85000

target = 0x7FF6B0EA2874

chunk_size = 4 * 1024 * 1024
buf = ctypes.create_string_buffer(chunk_size)
bytes_read = ctypes.c_size_t(0)

print(f"Scanning for call 0x{target:X} in .text...")
for offset in range(0, text_size, chunk_size - 16):
    cur_addr = text_start + offset
    n = min(chunk_size, text_size - offset)
    if ReadProcessMemory(h_proc, ctypes.c_void_p(cur_addr), buf, n, ctypes.byref(bytes_read)):
        data = bytes(buf)[:bytes_read.value]
        # Search for E8 disp32
        pos = 0
        while True:
            pos = data.find(b'\xE8', pos)
            if pos == -1 or pos + 5 > len(data):
                break
            disp = struct.unpack('<i', data[pos+1:pos+5])[0]
            call_site = cur_addr + pos
            dest = call_site + 5 + disp
            if dest == target:
                print(f"Call site @ 0x{call_site:016X}")
            pos += 1

CloseHandle(h_proc)
print("Done.")
