Hai nguồn bổ sung cho nhau:
1[Dump SWF/AIR client] → đọc class encode/decode, tên field, thứ tự write/read 2[Hook send/recv runtime] → xác nhận byte thực tế trên dây khớp với dump
Dump client (JPEXS, FFDec, decompiler ActionScript…) cho bạn logic: packet nào gọi hàm nào, body gồm những field gì, kiểu int / Boolean / UTF ra sao.
Hook Frida cho bạn bằng chứng: khi bạn di chuyển trong game, byte nào đổi, opcode nào bắn ra. Đối chiếu hai phía thì mới dịch packet ổn — chỉ dump mà không hook dễ sai offset; chỉ hook hex mà không dump thì đoán mò lâu.
1[Game logic] → encode → send() → Server 2[Game logic] ← decode ← recv() ← Server
Frida attach process (ví dụ FlashPlayerApp.exe), hook send / recv / WSASend / WSARecv trong WS2_32.dll — đọc buffer ngay trước khi byte ra mạng hoặc vừa vào process.
Sau khi cắt được một gói hoàn chỉnh, layout thường kiểu:
1[magic 2B] [length 2B] [... header cố định ...] [opcode] [clientId ...] [body]
Một số opcode chỉ là envelope. Ví dụ opcode PACKET_CODE_EXAMPLE bọc body — byte đầu body là sub-command (move, turn, wind…). Dump client sẽ chỉ ra class PacketAnalyzer / GameSocket viết sub-command thế nào; hook log giúp verify từng offset.
TCP là stream — một lần recv có thể dính nửa gói hoặc hai gói. Cần buffer theo chiều send / recv, quét magic header, đọc length, cắt đúng số byte:
1const HEADER = 0xabcd; // magic — tìm từ hex log thực tế 2const HDR_SIZE = 666; 3const PACKET_CODE_EXAMPLE = 42; 4 5const stream = []; // buffer tích lũy 6 7function u16(buf, off) { 8 return ((buf[off] << 8) | buf[off + 1]) & 0xffff; 9} 10 11function i16(buf, off) { 12 let v = u16(buf, off); 13 return v & 0x8000 ? v - 0x10000 : v; 14} 15 16function i32(buf, off) { 17 return ( 18 (buf[off] << 24) | 19 (buf[off + 1] << 16) | 20 (buf[off + 2] << 8) | 21 buf[off + 3] 22 ); 23} 24 25function processBytes(incoming) { 26 for (const b of incoming) stream.push(b & 0xff); 27 28 let off = 0; 29 while (off + HDR_SIZE <= stream.length) { 30 // sync magic 31 while (off + 1 < stream.length && u16(stream, off) !== HEADER) off++; 32 if (off + HDR_SIZE > stream.length) break; 33 34 const len = u16(stream, off + 2); 35 if (len < HDR_SIZE || len > 8192) { 36 off++; 37 continue; 38 } 39 if (off + len > stream.length) break; // chưa đủ byte — chờ recv sau 40 41 const pkt = stream.slice(off, off + len); 42 const code = i16(pkt, 6); 43 44 if (code === PACKET_CODE_EXAMPLE && pkt.length > HDR_SIZE) { 45 const subCmd = pkt[HDR_SIZE]; 46 console.log('PACKET_CODE_EXAMPLE subCmd=' + subCmd); 47 } 48 49 off += len; 50 } 51 stream.splice(0, off); // giữ phần dư cho lần sau 52}
Đoạn trên là khung — magic, HDR_SIZE, offset opcode lấy từ dump + vài lần test in hex. Khi khớp, mở rộng parser body theo sub-command từng bước.
Khi hook + parser chạy ổn, log Frida có thể feed ra overlay debug — tọa độ player/NPC, hướng, gió, góc UI so với góc packet:

| Từ dump client | Từ hook runtime |
|---|---|
writeInt(x), writeBoolean(b) | offset +4, giá trị đổi khi di chuyển |
| Tên packet / handler class | opcode xuất hiện đúng lúc vào phòng |
Sub-command enum MOVE = 1 | byte đầu body = 1 khi chỉ bấm đi |
Làm một hành động trong game → xem một dòng log → mở dump tìm hàm tương ứng → ghi offset → field. Lặp lại đến khi body của PACKET_CODE_EXAMPLE đọc được hết.