// REA-backed WinRT ABI for WhatsAppNativeProjection.dll build 2.2639.100.0. // Native calls only; no CDP, UI automation or restart. const native=Process.getModuleByName('WhatsAppNative.dll'); if(!native.path.includes('WhatsAppDesktop_2.2639.100.0_'))throw Error('Unsupported Desktop build; ABI must be revalidated with REA'); const combase=Process.getModuleByName('combase.dll'); function api(name,ret,args){return new NativeFunction(combase.getExportByName(name),ret,args);} const createString=api('WindowsCreateString','int',['pointer','uint','pointer']); const deleteString=api('WindowsDeleteString','int',['pointer']); const rawString=api('WindowsGetStringRawBuffer','pointer',['pointer','pointer']); function check(hr){if(hr<0)throw Error('HRESULT 0x'+(hr>>>0).toString(16));} function hstring(value){const out=Memory.alloc(8);check(createString(Memory.allocUtf16String(value),value.length,out));return out.readPointer();} function readHString(handle){if(handle.isNull())return '';const len=Memory.alloc(4);const p=rawString(handle,len);return p.readUtf16String(len.readU32());} function guid(hex){const p=Memory.alloc(16);p.writeByteArray(hex.match(/../g).map(x=>parseInt(x,16)));return p;} const dbIid=guid('eeb0ca996bf8523780d9cf1b934d62b5'); const stmtIid=guid('94f8c705a4cb4b3baa1b9b2ce2ff2734'); const propertyIid=guid('dd82d64b5475e9409a9b82654ede7e62'); function method(obj,slot,ret,args){return new NativeFunction(obj.readPointer().add(slot*8).readPointer(),ret,['pointer',...args]);} function release(obj){if(!obj.isNull())method(obj,2,'uint',[])(obj);} function query(obj,iid){const out=Memory.alloc(8);check(method(obj,0,'int',['pointer','pointer'])(obj,iid,out));return out.readPointer();} let references=[]; function property(value){if(value.isNull())return null;let p=ptr(0);try{ p=query(value,propertyIid);const out=Memory.alloc(8);check(method(p,19,'int',['pointer'])(p,out)); const h=out.readPointer();try{return readHString(h);}finally{deleteString(h);} }finally{release(p);release(value);}} function statement(db,sql){const h=hstring(sql),error=Memory.alloc(4),partial=Memory.alloc(8),out=Memory.alloc(8); try{check(method(db,9,'int',['pointer','pointer','pointer','pointer'])(db,h,error,partial,out)); // The integer out-parameter is the consumed SQL offset, not an error code. const original=out.readPointer();if(original.isNull())throw Error('No prepared statement'); try{return query(original,stmtIid);}finally{release(original);} }finally{deleteString(h);} } function select(db,sql,limit){let st=ptr(0);try{ st=statement(db,sql);const count=Memory.alloc(4);check(method(st,10,'int',['pointer'])(st,count)); const rows=[];while(rows.lengthv.toString(16).padStart(2,'0')).join(' '); let scanned=0,hitCount=0;const found=[],addresses=new Set(),failures=[]; for(const range of Process.enumerateRanges({protection:'rw-',coalesce:true})){ if(scanned>=maxMb*1024*1024)break; const size=Math.min(range.size,maxMb*1024*1024-scanned);scanned+=size; let hits=[];try{hits=Memory.scanSync(range.base,size,pattern);}catch{continue;} hitCount+=hits.length; for(const hit of hits){if(hit.address.equals(iface)||!hit.address.and(7).isNull())continue; let object=ptr(0);try{object=query(hit.address,dbIid);if(addresses.has(object.toString())){release(object);continue;} const tables=select(object,"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name",100); const id=references.length;references.push(object);addresses.add(object.toString()); found.push({id,address:object.toString(),tables:tables.map(r=>r[0])});object=ptr(0); }catch(error){if(failures.length<20)failures.push({address:hit.address.toString(),error:String(error)});if(!object.isNull())release(object);} } }return {databases:found,bytesScanned:scanned,vtable:vtable.toString(),vtableHits:hitCount,failures}; }finally{release(iface);release(sample);release(factory);deleteString(name);} }, search(id,term,limit){if(!Number.isInteger(id)||id<0||id>=references.length)throw Error('Unknown database'); if(!Number.isInteger(limit)||limit<1||limit>1000)throw Error('Invalid row limit'); const tokens=term.trim().split(/\s+/).filter(Boolean);if(!tokens.length)throw Error('Empty query'); const match=tokens.map(t=>'"'+t.replace(/"/g,'""')+'"*').join(' AND ').replace(/'/g,"''"); const sql="SELECT CAST(t.id AS TEXT),CAST(t.chatId AS TEXT),CAST(t.timestamp AS TEXT),CAST(t.text AS TEXT) FROM message t JOIN message_fts f ON t.rowid=f.rowid WHERE message_fts MATCH '"+match+"' ORDER BY rank LIMIT "+limit; return select(references[id],sql,limit); }, oldest(id,limit){ if(!Number.isInteger(id)||id<0||id>=references.length)throw Error('Unknown database'); if(!Number.isInteger(limit)||limit<1||limit>1000)throw Error('Invalid row limit'); return select(references[id],"SELECT CAST(id AS TEXT),CAST(chatId AS TEXT),CAST(timestamp AS TEXT),CAST(text AS TEXT) FROM message WHERE timestamp > 0 AND text IS NOT NULL AND length(text) > 0 ORDER BY timestamp,id LIMIT "+limit,limit); }, stats(id){return select(references[id],"SELECT CAST(COUNT(*) AS TEXT),CAST(MIN(timestamp) AS TEXT),CAST(MAX(timestamp) AS TEXT) FROM message",1)[0];}, indexbounds(id){return select(references[id],"SELECT CAST(COUNT(*) AS TEXT),CAST(COALESCE(MAX(rowid),0) AS TEXT) FROM message",1)[0];}, exportindex(id,after,upper,limit){ if(!Number.isInteger(id)||id<0||id>=references.length)throw Error('Unknown database'); if(!/^-?\d+$/.test(String(after))||!/^\d+$/.test(String(upper)))throw Error('Invalid row boundary'); if(!Number.isInteger(limit)||limit<1||limit>500)throw Error('Invalid batch size'); return select(references[id],"SELECT CAST(rowid AS TEXT),CAST(id AS TEXT),CAST(chatId AS TEXT),CAST(timestamp AS TEXT),CAST(text AS TEXT) FROM message WHERE rowid > "+after+" AND rowid <= "+upper+" ORDER BY rowid LIMIT "+limit,limit); }, close(){for(const object of references)release(object);references=[];} };