Files
GaCy-GO/tools/cw_native_craft_capture.js
2026-10-28 18:54:43 +07:00

21 lines
1.9 KiB
JavaScript

// Research only, verified assembly SHA256:
// c5e02e6e984f7e5577c78b995a8efeee2d06ee22e67d484ad5e859c8bf21e144.
// CraftConfigRow.FromBinary -> loader 1e0db310 -> table at GetAll 1e0dadf0.
// Getter/entry layout proven by 1e0db000 (24-byte entries, value at +16).
// Runs on the existing managed handbook callback; no gameplay actions/loaders.
const m=Process.getModuleByName('GameAssembly.dll');
const check=(rva,expected)=>{const a=Array.from(new Uint8Array(m.base.add(rva).readByteArray(expected.length)));if(a.some((x,i)=>x!==expected[i]))throw Error('Code identity differs');};
check(0x1e0dadf0,[0x48,0x83,0xec,0x28]);check(0x1d3efb60,[0x56,0x57,0x48,0x83,0xec,0x28]);
const all=new NativeFunction(m.base.add(0x1e0dadf0),'pointer',['pointer']);
const serialize=new NativeFunction(m.base.add(0x1d3efb60),'pointer',['pointer','pointer']);
let index=0,count=0,entries=null,done=false;
Interceptor.attach(m.base.add(0xdb5f860),{onLeave(){if(done)return;try{
if(entries===null){const d=all(ptr(0));if(d.isNull())throw Error('Craft table is missing');count=d.add(0x20).readU32();entries=d.add(0x18).readPointer();if(count>2048||entries.isNull()||entries.add(0x18).readU32()<count)throw Error('Craft dictionary bound');send({kind:'BEGIN',count});}
for(let batch=0;batch<24&&index<count;batch++){
const row=entries.add(0x30+24*index++).readPointer();if(row.isNull())continue;
const value=serialize(row,ptr(0));if(value.isNull())throw Error('Serialization is empty');const n=value.add(0x10).readS32();if(n<2||n>32768)throw Error('Craft row bound');const json=value.add(0x14).readUtf16String(n);const parsed=JSON.parse(json);if(!parsed.CraftID||!parsed.CraftEquipID||!Array.isArray(parsed.CostEquipList))throw Error('Craft schema differs');send({kind:'ROW',json});
}
if(index>=count){done=true;send({kind:'END',count});}
}catch(e){done=true;send({kind:'ERROR',error:String(e)});}}});
send({kind:'ATTACHED',scope:'flat craft rows only'});