// Research capture for SHA 4aa08a0706f27d2cf9951b61920c179140ece9ea94f7e34422c18e46922939b6. // Exact FromBinary -> Add dictionary -> GetAll chains are retained in // battle_diagnostics/preview-*-*.asm. Only flat config getters are called. const mod = Process.getModuleByName('GameAssembly.dll'); const tables = [['gameboy',0x1ded33b0]]; for (const [name,rva] of tables) { if (mod.base.add(rva).readU32() !== 0x28ec8348) throw Error('Getter prologue differs: '+name); } const serialize = new NativeFunction(mod.base.add(0x1d46c040),'pointer',['pointer','pointer']); let table=0,index=0,count=0,entries=null,done=false,busy=false; const marker='GAC_PREVIEW_TICK'; Interceptor.attach(mod.base.add(0x1d46c040),{ onEnter(args){this.trigger=false;if(done||busy)return;try{this.trigger=args[0].add(0x10).readS32()===marker.length&&args[0].add(0x14).readUtf16String(marker.length)===marker;}catch(e){}}, onLeave(){if(!this.trigger||busy||done)return;busy=true;try{ if(entries===null){const get=new NativeFunction(mod.base.add(tables[table][1]),'pointer',['pointer']);const dict=get(ptr(0));if(dict.isNull())throw Error('Missing dictionary');count=dict.add(0x20).readU32();entries=dict.add(0x18).readPointer();index=0;if(count>4096||entries.isNull()||entries.add(0x18).readU32()32768)throw Error('Serialization bound');rows.push(value.add(0x14).readUtf16String(length)); } send({kind:'ROWS',table:tables[table][0],rows}); if(index>=count){send({kind:'TABLE_END',table:tables[table][0],count});table++;entries=null;if(table===tables.length){done=true;send({kind:'END'});}} }catch(e){done=true;send({kind:'ERROR',error:String(e)});}finally{busy=false;}} }); send({kind:'ATTACHED',path:mod.path,scope:'four named flat config tables, 16 rows per managed marker callback'});