"""Bounded original Inventory helper trace, no process access or game writes.

Entry addresses come from the existing Toolkit anchor trace and its call graph.
Private Unicorn cases execute original bytes in synthetic memory only.
"""
import importlib.util
import json
import struct
import sys
from pathlib import Path
from capstone import Cs, CS_ARCH_X86, CS_MODE_64
from unicorn.x86_const import (UC_X86_REG_RAX, UC_X86_REG_RBX, UC_X86_REG_RBP,
    UC_X86_REG_RCX, UC_X86_REG_RDX, UC_X86_REG_RSI, UC_X86_REG_RSP,
    UC_X86_REG_R8, UC_X86_REG_R9, UC_X86_REG_R12, UC_X86_REG_RIP)

root = Path(__file__).resolve().parents[7]
# Run from the workspace, as the existing discovery scripts do.
sys.path.insert(0, 'src/loader/runtime/profile-tools/dev/tools/enshrouded')
from discovery.pe import PE

directory = Path(__file__).parent
anchor = json.loads((directory/'original-anchor-trace-20261016.json').read_text(encoding='utf-8'))
pe = PE(Path(anchor['sources']['executable']))
assert pe.identity() == anchor['image']
c = Cs(CS_ARCH_X86, CS_MODE_64)
reflection = json.loads(Path('src/loader/runtime/profile-tools/devdata/enshrouded-client-25966634-kfc4/live-discovery/reflection.json').read_text())
types = reflection['types']
by_name = {t['qualifiedName']: t for t in types}
tracking = json.loads(Path('src/loader/runtime/profile-tools/devdata/creative-mode-146/current-tracking-selection-inventory-20261016.json').read_text())
selection = {row['qualifiedName']: row for row in tracking['entries']}


def ins(begin, end):
    return [dict(rva=i.address, bytes=i.bytes.hex(), asm=i.mnemonic+' '+i.op_str)
        for i in c.disasm(pe.read(begin,end-begin),begin)]


def function(rva):
    fragments = [f for f in pe.functions if f['primaryBeginRva'] == rva]
    assert fragments, hex(rva)
    return dict(rva=rva, fragments=[dict(beginRva=f['beginRva'],endRva=f['endRva'],
        instructions=ins(f['beginRva'],f['endRva'])) for f in fragments])


def leaf(rva):
    output = []
    for i in c.disasm(pe.read(rva,0x600),rva):
        output.append(dict(rva=i.address,bytes=i.bytes.hex(),asm=i.mnemonic+' '+i.op_str))
        if i.mnemonic == 'ret':
            return dict(rva=rva,instructions=output)
    raise AssertionError('bounded leaf has no return: '+hex(rva))


def field_type(name):
    t = by_name[name]
    return dict(name=name,index=t['index'],size=t['size'],qualifiedHash=t['qualifiedHash'],
        innerType=types[t['innerType']]['qualifiedName'] if t['innerType'] is not None else None,
        fields=[dict(name=f['name'],offset=f['dataOffset'],type=types[f['type']]['qualifiedName'])
            for f in t['structFields'].values()],enumFields=t['enumFields'])


spec=importlib.util.spec_from_file_location('attribute_original_oracle',
    'src/loader/runtime/profile-tools/dev/tools/enshrouded/verify-attribute-vm.py')
module=importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
oracle=module.Oracle(pe)
u=oracle.uc
private=0x77000000
u.mem_map(private,0x4000)
rsp=oracle.STACK+0x8000


def put(address,fmt,*values): u.mem_write(address,struct.pack(fmt,*values))
def get(address,fmt): return struct.unpack(fmt,u.mem_read(address,struct.calcsize(fmt)))[0]
def registers(**values):
    names={'rax':UC_X86_REG_RAX,'rbx':UC_X86_REG_RBX,'rbp':UC_X86_REG_RBP,
        'rcx':UC_X86_REG_RCX,'rdx':UC_X86_REG_RDX,'rsi':UC_X86_REG_RSI,
        'rsp':UC_X86_REG_RSP,'r8':UC_X86_REG_R8,'r9':UC_X86_REG_R9,'r12':UC_X86_REG_R12}
    for name,register in names.items(): u.reg_write(register,values.get(name,0))


split_cases=[]
for count in (1,2,10,65535):
    for amount in sorted({0,1,count//2,count-1,count}):
        for clear_id in (False,True):
            put(private,'<III',42,count,0)
            put(rsp+0x20,'<I',count)
            put(rsp+0xb0,'<B',clear_id)
            registers(rsp=rsp,rsi=private,r12=amount)
            u.emu_start(pe.base+0x411ada,pe.base+0x411afe,timeout=100000,count=10000)
            assert u.reg_read(UC_X86_REG_RIP)==pe.base+0x411afe
            actual=struct.unpack('<III',u.mem_read(private,12))
            expected=(0 if clear_id and amount==count else 42,count-amount,0)
            assert actual==expected,(count,amount,clear_id,actual,expected)
            split_cases.append(dict(sourceCount=count,requestedAmount=amount,clearIdOnFullRemoval=clear_id,
                resultItemId=actual[0],resultCount=actual[1],resultPide=actual[2],
                subtractionSiteExecuted=amount!=count,originalBytesExecuted=True))

count_cases=[]
for item,count,pide in ((42,123,0),(42,123,99),(0,123,0),(42,1,0)):
    put(private,'<III',item,count,pide)
    put(private+0x100,'<QQ',0,0)
    put(rsp,'<Q',oracle.STOP)
    registers(rsp=rsp,rcx=private+0x100,rdx=private)
    u.emu_start(pe.base+0xe79210,oracle.STOP,timeout=100000,count=10000)
    assert u.reg_read(UC_X86_REG_RIP)==oracle.STOP
    status,available=get(private+0x100,'<B'),get(private+0x108,'<I')
    expected=(0x12,0) if not item else (0,1 if pide else count)
    assert (status,available)==expected
    count_cases.append(dict(itemId=item,count=count,pide=pide,status=status,availableCount=available))

flag_cases=[]
for flags in range(32):
    u.mem_write(private,b'\0'*0x100)
    put(private+0x25,'<B',flags)
    registers(rsp=rsp,rbp=rsp+0x1000,rsi=private,rbx=123)
    u.emu_start(pe.base+0x3f63bd,pe.base+0x3f63de,timeout=100000,count=10000)
    assert u.reg_read(UC_X86_REG_RIP)==pe.base+0x3f63de
    actual=get(rsp+0x28,'<B')
    expected=0 if flags&(1<<2) else 1
    assert actual==expected
    flag_cases.append(dict(transferFlags=flags,moveAllStacks=bool(flags&4),forwardedSixthArgument=actual))

split=next(a for a in anchor['anchors'] if a['name']=='Unlimited Stack Split')
assert split['matches']==[0x411b34] and split['sites'][0]['primaryFunctionRva']==0x411990
result=dict(image=pe.identity(),processOpened=False,processWrites=False,nativeGameCalls=False,
    sources=dict(anchorTrace=str(directory/'original-anchor-trace-20261016.json'),
        reflection='src/loader/runtime/profile-tools/devdata/enshrouded-client-25966634-kfc4/live-discovery/reflection.json',
        oldModifierProfile='src/loader/runtime/profiles/enshrouded/client/1076226.json'),
    originalSplitAnchor=dict(name=split['name'],siteRva=0x411b34,helperRva=0x411990,
        source='keen::ecs::Inventory.slots[slotIndex+1].data.count',slotStride=12,countOffset=4,
        scope='partial removal only, full removal clears count through another original branch'),
    originalSourceOwnership=dict(slotReferenceType='keen::InventorySlotId',entityField='entityId',slotField='slotIndex',
        unpackHelperRva=0xe837d0,packHelperRva=0xe65750,resolveHelperRva=0x40ed80,
        inventoryAccessorContextOffset=0x20,inventorySetupAccessorContextOffset=0x18,
        maximumSlotIndexExclusive=8,resolvedAddress='Inventory component + slotIndex*12',
        nonInventoryFallbackAccessorContextOffset=0xa8,
        fallbackScope='original helper37d210, no reflected storage claim established here'),
    currentOldItemUseAnalog=dict(oldFunctionRva=0x3857d0,currentCallgraphAnalogRva=0x40cb70,
        currentByteForwardRva=0x40cfc4,downstreamRvas=[0x40d8d0,0x40faa0],
        sourceRemovalCallRva=0x40cfa4,sourceRemovalHelperRva=0x411990,
        sourceRemovalPrecedesForward=True,
        originalInputProducer=dict(functionRva=0x3f5cd0,instructionRva=0x3f63bd,callRva=0x3f63e6,
            path='keen::ecs::PlayerInput.fromClient.inventoryTransferAction.flags',
            fieldOffset=0x25,enum='keen::ecs::TransferItemToInventoryFlag.MoveAllStacks',bit=2,
            formula='sixthArgument = !((flags >> MoveAllStacks) & 1)'),
        downstreamUses=['40faa0: byte0xe0 on incoming frame gates equipped-container filtering and extra slot selection',
            '40faa0: filter compares CharacterSetup equipped inventory links at+a0/+a4'],
        globalConsumeFalseEquivalentProven=False),
    componentPolicies={n:dict(selection=selection[n]['selection']) for n in
        ['keen::ecs::Inventory','keen::ecs::InventorySetup','keen::ecs::PlayerInput','keen::ecs::ServerConsumedPlayerInput']},
    reflectedTypes=[field_type(n) for n in ['keen::InventorySlotId','keen::ItemStack','keen::ItemData',
        'keen::ecs::Inventory','keen::ecs::InventorySetup','keen::ecs::InventoryTransferAction',
        'keen::InventoryActionType','keen::ecs::TransferItemToInventoryFlag','keen::actor::PayUsageCost',
        'keen::actor::ApplyBuffEvent','keen::actor::SpawnEntityBaseEvent','keen::actor::PlaceVoxelObjectEvent']],
    originalFunctions={name:function(rva) for name,rva in [('sourceRemoval',0x411990),('slotResolve',0x40ed80),
        ('transferAll',0x40cb70),('transferRemovedStack',0x40d8d0),('insertIntoOwner',0x40faa0),
        ('inventoryInputAction',0x3f5cd0),('inventoryActionsSystem',0x3fd000)]},
    leaves={name:leaf(rva) for name,rva in [('contextInitialize',0x21da10),('contextBind',0x411830),
        ('slotPack',0xe65750),('slotUnpack',0xe837d0)]},
    availableStackCountInstructions=ins(0xe79210,0xe7924a),
    splitCases=split_cases,countCases=count_cases,flagCases=flag_cases,
    privateOriginalCaseCount=len(split_cases)+len(count_cases)+len(flag_cases),
    limits=['No game process or live inventory transaction was opened or modified',
        'The old modifier function mapping is callgraph/operand correspondence, not an old/current binary identity claim',
        'No resource count or input enum has been shown to suppress only the partial source subtraction',
        'Inventory is selected by both tracking categories, so the current untracked-component direct writer rejects it',
        'Event.consume paths are real reflection fields. Their separate consumer provenance is being traced in the usage-cost work',
        'No mod migration, native function call or substitute refill loop is introduced'])
output=directory/'inventory-stack-dataflow-20261016.json'
output.write_text(json.dumps(result,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
print(json.dumps(dict(path=str(output),privateOriginalCases=result['privateOriginalCaseCount'],passed=True)))
