"""Read-only native observation of the receipt-owned offline test process."""
import ctypes as c
from ctypes import wintypes as w
import hashlib
import json
import pathlib
import struct
import sys
PIN = 'e6be8bbe04e6a7ae226d4318945af7f367de13dc5a007a261964d9ba8144e988'
IMAGE = pathlib.Path(r'E:\Elite Dangerous\Products\elite-dangerous-odyssey-64\EliteDangerous64.exe')
class Module(c.Structure):
_fields_ = [('size', w.DWORD), ('id', w.DWORD), ('pid', w.DWORD),
('globalUsage', w.DWORD), ('processUsage', w.DWORD),
('base', c.c_void_p), ('bytes', w.DWORD), ('handle', w.HMODULE),
('name', w.WCHAR * 256), ('path', w.WCHAR * 260)]
class OwnedNativeProcess:
def __init__(self, receipt_path):
self.receipt_path = pathlib.Path(receipt_path).resolve(strict=True)
self.receipt = json.loads(self.receipt_path.read_text(encoding='utf-8-sig'))
self.handle = None
self.lease = None
if (self.receipt.get('mode') != 'in-process-offline'
or not self.receipt.get('zeroNetworkCapabilities')
or self.receipt.get('online')
or self.receipt.get('imageSha256') != PIN):
raise ValueError('An offline-only pinned test launch is required')
script_root = self.receipt_path.parents[2] / 'scripts' / 'capture'
sys.path.insert(0, str(script_root))
from owned_diagnostic_lock import acquire_native_lease
self.lease = acquire_native_lease(self.receipt_path)
try:
self.pid = int(self.receipt['ProcessId'])
self.kernel = k = c.WinDLL('kernel32', use_last_error=True)
k.OpenProcess.argtypes = [w.DWORD, w.BOOL, w.DWORD]
k.OpenProcess.restype = w.HANDLE
k.CloseHandle.argtypes = [w.HANDLE]
k.ReadProcessMemory.argtypes = [w.HANDLE, c.c_void_p, c.c_void_p, c.c_size_t, c.POINTER(c.c_size_t)]
k.QueryFullProcessImageNameW.argtypes = [w.HANDLE, w.DWORD, w.LPWSTR, c.POINTER(w.DWORD)]
k.CreateToolhelp32Snapshot.argtypes = [w.DWORD, w.DWORD]
k.CreateToolhelp32Snapshot.restype = w.HANDLE
k.Module32FirstW.argtypes = k.Module32NextW.argtypes = [w.HANDLE, c.POINTER(Module)]
self.handle = k.OpenProcess(0x410, False, self.pid)
if not self.handle:
raise c.WinError(c.get_last_error())
image = c.create_unicode_buffer(32768)
length = w.DWORD(len(image))
if not k.QueryFullProcessImageNameW(self.handle, 0, image, c.byref(length)):
raise c.WinError(c.get_last_error())
if pathlib.Path(image.value) != IMAGE or hashlib.sha256(IMAGE.read_bytes()).hexdigest() != PIN:
raise ValueError('The exact offline test image is not running')
snapshot = k.CreateToolhelp32Snapshot(0x18, self.pid)
if snapshot == c.c_void_p(-1).value:
raise c.WinError(c.get_last_error())
self.modules = []
try:
entry = Module()
entry.size = c.sizeof(entry)
found = k.Module32FirstW(snapshot, c.byref(entry))
while found:
self.modules.append((pathlib.Path(entry.path), entry.base, entry.bytes))
found = k.Module32NextW(snapshot, c.byref(entry))
finally:
k.CloseHandle(snapshot)
runtime = pathlib.Path(self.receipt['ownedNativeRuntime']).resolve(strict=True)
if not any(path == runtime for path, _, _ in self.modules):
raise ValueError('The receipt-owned runtime is absent')
self.base, self.image_size = next((base, size) for path, base, size in self.modules if path == IMAGE)
except BaseException:
self.close()
raise
def close(self):
if self.handle:
self.kernel.CloseHandle(self.handle)
self.handle = None
if self.lease:
self.lease.close()
self.lease = None
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def read(self, address, size):
if address < 0x10000 or not 0 < size <= 8192:
raise ValueError('Invalid bounded memory read')
buffer = c.create_string_buffer(size)
done = c.c_size_t()
if not self.kernel.ReadProcessMemory(self.handle, address, buffer, size, c.byref(done)) or done.value != size:
raise OSError('The observed object is no longer readable')
return buffer.raw
def value(self, address, fmt):
return struct.unpack(fmt, self.read(address, struct.calcsize(fmt)))[0]
def ptr(self, address):
return self.value(address, '<Q')
def rva(self, address):
return hex(address - self.base) if self.base <= address < self.base + self.image_size else None
def ship_seeds(self):
seeds = {}
for path in self.receipt_path.parent.glob('entity-*.jsonl'):
if path.stat().st_size > 32 * 1024 * 1024:
continue
for line in path.read_text(encoding='utf-8-sig').splitlines():
try:
row = json.loads(line)
if row.get('kind') == 'native-entity-observed' and row.get('type') == 'SideWinder':
seeds[row['id']] = int(row['lifecyclePointer'], 16)
except (ValueError, KeyError):
pass
return seeds
"""Read-only native observation of the receipt-owned offline test process."""
import ctypes as c
from ctypes import wintypes as w
import hashlib
import json
import pathlib
import struct
import sys
PIN = 'e6be8bbe04e6a7ae226d4318945af7f367de13dc5a007a261964d9ba8144e988'
IMAGE = pathlib.Path(r'E:\Elite Dangerous\Products\elite-dangerous-odyssey-64\EliteDangerous64.exe')
class Module(c.Structure):
_fields_ = [('size', w.DWORD), ('id', w.DWORD), ('pid', w.DWORD),
('globalUsage', w.DWORD), ('processUsage', w.DWORD),
('base', c.c_void_p), ('bytes', w.DWORD), ('handle', w.HMODULE),
('name', w.WCHAR * 256), ('path', w.WCHAR * 260)]
class OwnedNativeProcess:
def __init__(self, receipt_path):
self.receipt_path = pathlib.Path(receipt_path).resolve(strict=True)
self.receipt = json.loads(self.receipt_path.read_text(encoding='utf-8-sig'))
self.handle = None
self.lease = None
if (self.receipt.get('mode') != 'in-process-offline'
or not self.receipt.get('zeroNetworkCapabilities')
or self.receipt.get('online')
or self.receipt.get('imageSha256') != PIN):
raise ValueError('An offline-only pinned test launch is required')
script_root = self.receipt_path.parents[2] / 'scripts' / 'capture'
sys.path.insert(0, str(script_root))
from owned_diagnostic_lock import acquire_native_lease
self.lease = acquire_native_lease(self.receipt_path)
try:
self.pid = int(self.receipt['ProcessId'])
self.kernel = k = c.WinDLL('kernel32', use_last_error=True)
k.OpenProcess.argtypes = [w.DWORD, w.BOOL, w.DWORD]
k.OpenProcess.restype = w.HANDLE
k.CloseHandle.argtypes = [w.HANDLE]
k.ReadProcessMemory.argtypes = [w.HANDLE, c.c_void_p, c.c_void_p, c.c_size_t, c.POINTER(c.c_size_t)]
k.QueryFullProcessImageNameW.argtypes = [w.HANDLE, w.DWORD, w.LPWSTR, c.POINTER(w.DWORD)]
k.CreateToolhelp32Snapshot.argtypes = [w.DWORD, w.DWORD]
k.CreateToolhelp32Snapshot.restype = w.HANDLE
k.Module32FirstW.argtypes = k.Module32NextW.argtypes = [w.HANDLE, c.POINTER(Module)]
self.handle = k.OpenProcess(0x410, False, self.pid)
if not self.handle:
raise c.WinError(c.get_last_error())
image = c.create_unicode_buffer(32768)
length = w.DWORD(len(image))
if not k.QueryFullProcessImageNameW(self.handle, 0, image, c.byref(length)):
raise c.WinError(c.get_last_error())
if pathlib.Path(image.value) != IMAGE or hashlib.sha256(IMAGE.read_bytes()).hexdigest() != PIN:
raise ValueError('The exact offline test image is not running')
snapshot = k.CreateToolhelp32Snapshot(0x18, self.pid)
if snapshot == c.c_void_p(-1).value:
raise c.WinError(c.get_last_error())
self.modules = []
try:
entry = Module()
entry.size = c.sizeof(entry)
found = k.Module32FirstW(snapshot, c.byref(entry))
while found:
self.modules.append((pathlib.Path(entry.path), entry.base, entry.bytes))
found = k.Module32NextW(snapshot, c.byref(entry))
finally:
k.CloseHandle(snapshot)
runtime = pathlib.Path(self.receipt['ownedNativeRuntime']).resolve(strict=True)
if not any(path == runtime for path, _, _ in self.modules):
raise ValueError('The receipt-owned runtime is absent')
self.base, self.image_size = next((base, size) for path, base, size in self.modules if path == IMAGE)
except BaseException:
self.close()
raise
def close(self):
if self.handle:
self.kernel.CloseHandle(self.handle)
self.handle = None
if self.lease:
self.lease.close()
self.lease = None
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def read(self, address, size):
if address < 0x10000 or not 0 < size <= 8192:
raise ValueError('Invalid bounded memory read')
buffer = c.create_string_buffer(size)
done = c.c_size_t()
if not self.kernel.ReadProcessMemory(self.handle, address, buffer, size, c.byref(done)) or done.value != size:
raise OSError('The observed object is no longer readable')
return buffer.raw
def value(self, address, fmt):
return struct.unpack(fmt, self.read(address, struct.calcsize(fmt)))[0]
def ptr(self, address):
return self.value(address, '<Q')
def rva(self, address):
return hex(address - self.base) if self.base <= address < self.base + self.image_size else None
def ship_seeds(self):
seeds = {}
for path in self.receipt_path.parent.glob('entity-*.jsonl'):
if path.stat().st_size > 32 * 1024 * 1024:
continue
for line in path.read_text(encoding='utf-8-sig').splitlines():
try:
row = json.loads(line)
if row.get('kind') == 'native-entity-observed' and row.get('type') == 'SideWinder':
seeds[row['id']] = int(row['lifecyclePointer'], 16)
except (ValueError, KeyError):
pass
return seeds