feat: 探针改整帧 hex 记录 + 新增 probe_log_to_bins.js 接现有解码流水线
- sockets/probe_railgun.js: SEND 记录整帧字节(流密码跨消息连续,截断/丢帧会导致后续解密全错) - 3DTank/tools/probe_log_to_bins.js: 把日志里的 c2s 帧还原成 <out>/<label>/session_001/c2s_*.bin,供 decrypt_frames.js → dump_plain.js → decode_space_frames.js 复用
This commit is contained in:
1 parent
d4c8a843e2
commit
651e29f8b2
2 files changed
+71
-9
No files matched your search
@@ -0,0 +1,63 @@
|
||||
// probe_log_to_bins.js — 把页面探针(sockets/probe_railgun.js)的 WS 上行记录还原成 .bin,
|
||||
// 以便**直接复用**仓库里现成的解密/解码流水线:
|
||||
// decrypt_frames.js → dump_plain.js → decode_space_frames.js
|
||||
//
|
||||
// 输入(3DTank/names/probe_railgun.log)中的行:
|
||||
// [+1234ms] SEND len=48 hex=e22e2b2a5f80... ← 整帧 hex(探针已改成整帧记录)
|
||||
// 输出:<out>/<label>/session_001/c2s_0000.bin … ← 严格按出现顺序
|
||||
//
|
||||
// ⚠ 必须整帧且不丢帧:流密码状态跨消息连续,截断/漏掉任何一帧都会让后续解密全错。
|
||||
//
|
||||
// 用法:
|
||||
// node probe_log_to_bins.js [--log ../names/probe_railgun.log] [--out ../packets/probe] [--label probe-01]
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const PROJECT = path.resolve(__dirname, '..');
|
||||
const Argv = process.argv.slice(2);
|
||||
const Arg = (Name, Def) => {
|
||||
const I = Argv.indexOf('--' + Name);
|
||||
return I >= 0 && Argv[I + 1] ? Argv[I + 1] : Def;
|
||||
};
|
||||
|
||||
const LOG = path.resolve(Arg('log', path.join(PROJECT, 'names', 'probe_railgun.log')));
|
||||
const OUT = path.resolve(Arg('out', path.join(PROJECT, 'packets', 'probe')));
|
||||
const LABEL = Arg('label', 'probe-01');
|
||||
const SESS = path.join(OUT, LABEL, 'session_001');
|
||||
|
||||
if (!fs.existsSync(LOG)) {
|
||||
console.error('[ERROR] 日志不存在: ' + LOG);
|
||||
process.exit(1);
|
||||
}
|
||||
fs.mkdirSync(SESS, { recursive: true });
|
||||
|
||||
const Re = /^\[([^\]]+)\]\s+SEND\s+(?:blob\s+)?len=(\d+)\s+hex=([0-9a-fA-F]*)\s*$/;
|
||||
const Lines = fs.readFileSync(LOG, 'utf8').split(/\r?\n/);
|
||||
let N = 0;
|
||||
let Bad = 0;
|
||||
let Bytes = 0;
|
||||
const LogLines = [];
|
||||
for (const Line of Lines) {
|
||||
const M = Re.exec(Line);
|
||||
if (!M) continue;
|
||||
const T = M[1];
|
||||
const Len = Number(M[2]);
|
||||
const Hex = M[3];
|
||||
if (Hex.length !== Len * 2) {
|
||||
Bad++;
|
||||
LogLines.push('SKIP(hex 长度与 len 不符) ' + Line);
|
||||
continue;
|
||||
}
|
||||
fs.writeFileSync(path.join(SESS, 'c2s_' + String(N).padStart(4, '0') + '.bin'), Buffer.from(Hex, 'hex'));
|
||||
LogLines.push(T + ' c2s_' + String(N).padStart(4, '0') + '.bin len=' + Len);
|
||||
N++;
|
||||
Bytes += Len;
|
||||
}
|
||||
fs.writeFileSync(path.join(SESS, 'log.txt'), LogLines.join('\n') + '\n', 'utf8');
|
||||
|
||||
console.log('[OK] 还原 c2s 帧 ' + N + ' 个 ' + Bytes + ' 字节(跳过 ' + Bad + ' 条)');
|
||||
console.log(' 输出目录: ' + SESS);
|
||||
console.log(' 下一步:');
|
||||
console.log(' node decrypt_frames.js --dir ' + OUT + ' --label ' + LABEL);
|
||||
console.log(' node dump_plain.js --session ' + LABEL + ' --out ..\\packets\\plain\\' + LABEL);
|
||||
console.log(' node decode_space_frames.js --dir ..\\packets\\plain\\' + LABEL + ' --side c2s --fields');
|
||||
@@ -51,7 +51,9 @@
|
||||
if (Buf.length >= 40) flush();
|
||||
};
|
||||
|
||||
// ---- ③ WS 上行记录(游戏发的是 Blob,必须异步读,否则 hex 恒为空)----
|
||||
// ---- ③ WS 上行记录(**完整帧字节**)----
|
||||
// 必须整帧记录:流密码状态跨消息连续,缺/截断任何一帧都会让后续解密全错。
|
||||
// 实测游戏发的是 DataView(有 byteLength、无 slice),故走 buffer+byteOffset。
|
||||
var Hex = function (u) {
|
||||
var h = '';
|
||||
for (var i = 0; i < u.length; i++) h += (u[i] < 16 ? '0' : '') + u[i].toString(16);
|
||||
@@ -65,20 +67,17 @@
|
||||
try {
|
||||
if (d instanceof Blob) {
|
||||
var size = d.size;
|
||||
d.slice(0, 24).arrayBuffer().then(function (ab) {
|
||||
d.slice(0, size).arrayBuffer().then(function (ab) {
|
||||
Buf.push('[+' + Now() + 'ms] SEND blob len=' + size + ' hex=' + Hex(new Uint8Array(ab)));
|
||||
}).catch(function () {});
|
||||
} else if (d && d.byteLength !== undefined) {
|
||||
// 注意:游戏实际发的是 DataView / TypedArray / ArrayBuffer(实测 DataView),
|
||||
// 且有 .byteLength 但没有 .slice —— 必须走 buffer+byteOffset 取视图,
|
||||
// 否则 new Uint8Array(dataView, 0, 24) 会得到长度 0 的视图(hex 恒为空)。
|
||||
var View;
|
||||
if (d.buffer && typeof d.byteOffset === 'number') {
|
||||
View = new Uint8Array(d.buffer, d.byteOffset, Math.min(24, d.byteLength));
|
||||
View = new Uint8Array(d.buffer, d.byteOffset, d.byteLength);
|
||||
} else if (typeof d.slice === 'function') {
|
||||
View = new Uint8Array(d.slice(0, 24));
|
||||
View = new Uint8Array(d.slice(0, d.byteLength));
|
||||
} else {
|
||||
View = new Uint8Array(d, 0, Math.min(24, d.byteLength));
|
||||
View = new Uint8Array(d, 0, d.byteLength);
|
||||
}
|
||||
Buf.push('[+' + Now() + 'ms] SEND len=' + d.byteLength + ' hex=' + Hex(View));
|
||||
}
|
||||
@@ -86,7 +85,7 @@
|
||||
return Orig.apply(this, arguments);
|
||||
};
|
||||
W.prototype.__probeWrapped__ = true;
|
||||
Buf.push('WS send 已挂(Blob 异步读,记前 24 字节)');
|
||||
Buf.push('WS send 已挂(整帧 hex 记录,供离线解密解码)');
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
|
||||
Reference in new issue
Block a user