"""Offline emulation of the local toolkit's registration constructor.

No native loading, injection, network, game-memory writes, or host API bridging.
Emulated memset writes only Unicorn memory. Images and stack are private buffers.
"""
import json
import struct
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[6]
sys.path.insert(0, str(ROOT / 'src/loader/runtime/profile-tools/dev/tools/enshrouded'))
from discovery.pe import PE
from unicorn import Uc, UC_ARCH_X86, UC_MODE_64, UC_HOOK_CODE
from unicorn.x86_const import UC_X86_REG_RIP, UC_X86_REG_RSP, UC_X86_REG_RAX, UC_X86_REG_RCX, UC_X86_REG_RDX, UC_X86_REG_R8, UC_X86_REG_R9

folder = Path(__file__).resolve().parent
pe = PE(folder / 'resource-10-300-1033.dll')
uc = Uc(UC_ARCH_X86, UC_MODE_64)
uc.mem_map(pe.base, (pe.image_size + 4095) & ~4095)
uc.mem_write(pe.base, pe.data[:pe.headers_size])
for section in pe.sections:
    uc.mem_write(pe.base + section['rva'], pe.data[section['raw']:section['raw'] + section['rawSize']])
stack, stack_size, stop = 0x70000000, 0x200000, 0x71000000
uc.mem_map(stack, stack_size)
uc.mem_map(stop, 4096)
rsp = stack + stack_size - 0x1008
uc.mem_write(rsp, struct.pack('<Q', stop))
uc.reg_write(UC_X86_REG_RSP, rsp)
calls = []

def string(address):
    if not pe.base <= address < pe.base + pe.image_size:
        return None
    return bytes(uc.mem_read(address, min(512, pe.base + pe.image_size - address))).split(b'\0')[0].decode('utf-8', errors='replace')

def return_from_helper(result=None):
    if result is not None:
        uc.reg_write(UC_X86_REG_RAX, result)
    current_rsp = uc.reg_read(UC_X86_REG_RSP)
    destination = struct.unpack('<Q', uc.mem_read(current_rsp, 8))[0]
    uc.reg_write(UC_X86_REG_RSP, current_rsp + 8)
    uc.reg_write(UC_X86_REG_RIP, destination)

def on_code(_, address, size, data):
    rva = address - pe.base
    if rva == 0x48010:
        dest, value, length = (uc.reg_read(reg) for reg in (UC_X86_REG_RCX, UC_X86_REG_RDX, UC_X86_REG_R8))
        if length > 0x10000:
            raise ValueError('unexpected emulated memset size')
        uc.mem_write(dest, bytes([value & 255]) * length)
        return_from_helper(dest)
    elif rva in (0x5050, 0x4e70):
        calls.append({'helperRva':rva, 'name':string(uc.reg_read(UC_X86_REG_RCX))})

uc.hook_add(UC_HOOK_CODE, on_code)
uc.emu_start(pe.base + 0x3a1b0, stop, timeout=15_000_000, count=3_000_000)
returned = uc.reg_read(UC_X86_REG_RIP) == stop
if not returned:
    raise ValueError('constructor did not return within the CPU budget')
count = struct.unpack('<I', uc.mem_read(pe.base + 0x23bbc8, 4))[0]
if not 0 < count <= 64:
    raise ValueError('unexpected registration count')
table = bytes(uc.mem_read(pe.base + 0x212370, count * 0xa38))
(folder / 'emulated-registration-table.bin').write_bytes(table)
rows = []
for i in range(count):
    record = table[i * 0xa38:(i + 1) * 0xa38]
    u32 = lambda offset: struct.unpack_from('<I', record, offset)[0]
    qword = lambda offset: struct.unpack_from('<Q', record, offset)[0]
    ints = lambda offset, n: list(struct.unpack_from('<' + 'i' * n, record, offset))
    sig_count = u32(0x124)
    if sig_count > 64:
        raise ValueError('invalid signature count')
    signature = ints(0x24, sig_count)
    row = {'index':i, 'name':string(qword(0)), 'signature': ' '.join('??' if b == -1 else f'{b:02X}' for b in signature),
           'kind20':u32(0x20), 'value10':u32(0x10)}
    if u32(0x20) == 0 and sig_count:
        # Original registration helper 0x4e70 stores its fifth argument at
        # +0x130, replacement dwords at +0x134 and their count at +0x174.
        # +0x10 is a status value, not the intervention offset or byte length.
        replacement_count = u32(0x174)
        if replacement_count > 16:
            raise ValueError('invalid simple replacement count')
        replacement = ints(0x134, replacement_count)
        if any(not 0 <= byte <= 255 for byte in replacement):
            raise ValueError('invalid simple replacement byte')
        row.update(siteOffset130=u32(0x130), replacementBytes=' '.join(f'{b:02X}' for b in replacement))
    if u32(0x20) == 1:
        row.update(siteOffset178=u32(0x178), originalPrefixSkip17c=u32(0x17c), originalSuffixSkip784=u32(0x784))
        for key, offset, length_offset, maximum in [('cavePrefix',0x180,0x780,384),('caveTrailer',0x788,0x7a8,8)]:
            n = u32(length_offset)
            if n > maximum:
                raise ValueError(f'invalid {key} count')
            row[key] = ' '.join(f'{b:02X}' for b in ints(offset,n))
        row['parameters7c0'] = record[0x7c0:0x7e8].hex(' ')
    rows.append(row)
output = {'method':'bounded-private-unicorn-constructor-emulation', 'image':pe.identity(),
          'constructorRva':0x3a1b0,'tableRva':0x212370,'recordStride':0xa38,'returned':returned,
          'registeredCount':count,'registrationHelperCalls':calls,'entries':rows,
          'limitations':['Registration data describes intended modifications. No gameplay effect was executed or verified.','All addresses belong to this exact local DLL SHA256.']}
(folder / 'complete-registration-catalog.json').write_text(json.dumps(output, indent=2),encoding='utf-8')
print(json.dumps({'returned':returned,'registeredCount':count,'helperCalls':len(calls),'names':[row['name'] for row in rows]},ensure_ascii=False))
