Files
xyzw_web_helper/xyzw_web_helper-main开源源码更新/src/utils/bonProtocol.js

779 lines
19 KiB
JavaScript
Raw Normal View History

2025-10-17 20:56:50 +08:00
/**
* BON (Binary Object Notation) 协议实现
* 基于提供的真实 BON 源码重新实现
*/
import lz4 from 'lz4js';
// -----------------------------
// BON 编解码器核心实现
// -----------------------------
export class Int64 {
constructor(high, low) {
this.high = high;
this.low = low;
}
}
export class DataReader {
constructor(bytes) {
this._data = bytes || new Uint8Array(0);
this._view = null;
this.position = 0;
}
get data() { return this._data; }
get dataView() {
return this._view || (this._view = new DataView(this._data.buffer, this._data.byteOffset, this._data.byteLength));
}
reset(bytes) {
this._data = bytes;
this.position = 0;
this._view = null;
}
validate(n) {
if (this.position + n > this._data.length) {
console.error('read eof');
return false;
}
return true;
}
readUInt8() {
if (!this.validate(1)) return;
return this._data[this.position++];
}
readInt16() {
if (!this.validate(2)) return;
const v = this._data[this.position++] | (this._data[this.position++] << 8);
return (v << 16) >> 16;
}
readInt32() {
if (!this.validate(4)) return;
const v = this._data[this.position++] | (this._data[this.position++] << 8) | (this._data[this.position++] << 16) | (this._data[this.position++] << 24);
return v | 0;
}
readInt64() {
const lo = this.readInt32();
if (lo === undefined) return;
let _lo = lo;
if (_lo < 0) _lo += 0x100000000;
const hi = this.readInt32();
if (hi === undefined) return;
return _lo + 0x100000000 * hi;
}
readFloat32() {
if (!this.validate(4)) return;
const v = this.dataView.getFloat32(this.position, true);
this.position += 4;
return v;
}
readFloat64() {
if (!this.validate(8)) return;
const v = this.dataView.getFloat64(this.position, true);
this.position += 8;
return v;
}
read7BitInt() {
let value = 0;
let shift = 0;
let b = 0;
let count = 0;
do {
if (count++ === 35) throw new Error('Format_Bad7BitInt32');
b = this.readUInt8();
value |= (b & 0x7F) << shift;
shift += 7;
} while ((b & 0x80) !== 0);
return value >>> 0;
}
readUTF() {
const len = this.read7BitInt();
return this.readUTFBytes(len);
}
readUint8Array(length, copy = false) {
const start = this.position;
const end = start + length;
const out = copy ? this._data.slice(start, end) : this._data.subarray(start, end);
this.position = end;
return out;
}
readUTFBytes(length) {
if (length === 0) return '';
if (!this.validate(length)) return;
const str = new TextDecoder('utf8').decode(this._data.subarray(this.position, this.position + length));
this.position += length;
return str;
}
}
let _shared = new Uint8Array(524288); // 512 KB initial buffer
export class DataWriter {
constructor() {
this.position = 0;
this._view = null;
this.data = _shared;
}
get dataView() {
return this._view || (this._view = new DataView(this.data.buffer, 0, this.data.byteLength));
}
reset() {
this.data = _shared;
this._view = null;
this.position = 0;
}
ensureBuffer(size) {
if (this.position + size <= _shared.byteLength) return;
const prev = _shared;
const need = this.position + size;
const nextLen = Math.max(Math.floor((_shared.byteLength * 12) / 10), need);
_shared = new Uint8Array(nextLen);
_shared.set(prev, 0);
this.data = _shared;
this._view = null;
}
writeInt8(v) {
this.ensureBuffer(1);
this.data[this.position++] = v | 0;
}
writeInt16(v) {
this.ensureBuffer(2);
this.data[this.position++] = v | 0;
this.data[this.position++] = (v >> 8) & 0xFF;
}
writeInt32(v) {
this.ensureBuffer(4);
this.data[this.position++] = v | 0;
this.data[this.position++] = (v >> 8) & 0xFF;
this.data[this.position++] = (v >> 16) & 0xFF;
this.data[this.position++] = (v >> 24) & 0xFF;
}
writeInt64(v) {
this.writeInt32(v);
if (v < 0) {
this.writeInt32(~Math.floor((-v) / 0x100000000));
} else {
this.writeInt32(Math.floor(v / 0x100000000) | 0);
}
}
writeFloat32(v) {
this.ensureBuffer(4);
this.dataView.setFloat32(this.position, v, true);
this.position += 4;
}
writeFloat64(v) {
this.ensureBuffer(8);
this.dataView.setFloat64(this.position, v, true);
this.position += 8;
}
_write7BitInt(v) {
let n = v >>> 0;
while (n >= 0x80) {
this.data[this.position++] = (n & 0xFF) | 0x80;
n >>>= 7;
}
this.data[this.position++] = n & 0x7F;
}
write7BitInt(v) {
this.ensureBuffer(5);
this._write7BitInt(v);
}
_7BitIntLen(v) {
return v < 0 ? 5
: v < 0x80 ? 1
: v < 0x4000 ? 2
: v < 0x200000 ? 3
: v < 0x10000000 ? 4
: 5;
}
writeUTF(str) {
const t = str.length;
if (t === 0) {
this.write7BitInt(0);
return;
}
const max = 6 * t;
this.ensureBuffer(5 + max);
const start = this.position;
this.position += this._7BitIntLen(max);
const from = this.position;
const reserved = from - start;
const encoder = new TextEncoder();
const { written } = encoder.encodeInto(str, this.data.subarray(this.position));
this.position += written;
const after = this.position;
const size = after - from;
this.position = start;
this._write7BitInt(size);
const used = this.position - start;
if (used !== reserved) {
this.data.copyWithin(from + (used - reserved), from, after);
}
this.position = from + size + (used - reserved);
}
writeUint8Array(src, offset = 0, length) {
const start = offset | 0;
const end = Math.min(src.byteLength, start + (length ?? src.byteLength));
const n = end - start;
if (n <= 0) return;
this.ensureBuffer(n);
this.data.set(src.subarray(start, end), this.position);
this.position += n;
}
writeUTFBytes(str) {
this.ensureBuffer(6 * str.length);
const encoder = new TextEncoder();
const { written } = encoder.encodeInto(str, this.data.subarray(this.position));
this.position += written;
}
getBytes(clone = false) {
return clone ? this.data.slice(0, this.position) : this.data.subarray(0, this.position);
}
}
export class BonEncoder {
constructor() {
this.dw = new DataWriter();
this.strMap = new Map();
}
reset() {
this.dw.reset();
this.strMap.clear();
}
encodeInt(v) {
this.dw.writeInt8(1);
this.dw.writeInt32(v | 0);
}
encodeLong(v) {
this.dw.writeInt8(2);
if (typeof v === 'number') {
this.dw.writeInt64(v);
} else {
this.dw.writeInt32(v.low | 0);
this.dw.writeInt32(v.high | 0);
}
}
encodeFloat(v) {
this.dw.writeInt8(3);
this.dw.writeFloat32(v);
}
encodeDouble(v) {
this.dw.writeInt8(4);
this.dw.writeFloat64(v);
}
encodeNumber(v) {
if ((v | 0) === v) this.encodeInt(v);
else if (Math.floor(v) === v) this.encodeLong(v);
else this.encodeDouble(v);
}
encodeString(s) {
const hit = this.strMap.get(s);
if (hit !== undefined) {
this.dw.writeInt8(99); // StringRef
this.dw.write7BitInt(hit);
return;
}
this.dw.writeInt8(5); // String
this.dw.writeUTF(s);
this.strMap.set(s, this.strMap.size);
}
encodeBoolean(b) {
this.dw.writeInt8(6);
this.dw.writeInt8(b ? 1 : 0);
}
encodeNull() {
this.dw.writeInt8(0);
}
encodeDateTime(d) {
this.dw.writeInt8(10);
this.dw.writeInt64(d.getTime());
}
encodeBinary(u8) {
this.dw.writeInt8(7);
this.dw.write7BitInt(u8.byteLength);
this.dw.writeUint8Array(u8);
}
encodeArray(arr) {
this.dw.writeInt8(9);
this.dw.write7BitInt(arr.length);
for (let i = 0; i < arr.length; i++) this.encode(arr[i]);
}
encodeMap(mp) {
this.dw.writeInt8(8);
this.dw.write7BitInt(mp.size);
mp.forEach((v, k) => {
this.encode(k);
this.encode(v);
});
}
encodeObject(obj) {
this.dw.writeInt8(8);
const keys = [];
for (const k in obj) {
if (!Object.prototype.hasOwnProperty.call(obj, k)) continue;
if (k.startsWith('_')) continue;
const type = typeof obj[k];
if (type === 'function' || type === 'undefined') continue;
keys.push(k);
}
this.dw.write7BitInt(keys.length);
for (const k of keys) {
this.encode(k);
this.encode(obj[k]);
}
}
encode(v) {
if (v == null) {
this.encodeNull();
return;
}
switch (v.constructor) {
case Number:
this.encodeNumber(v);
return;
case Boolean:
this.encodeBoolean(v);
return;
case String:
this.encodeString(v);
return;
case Int64:
this.encodeLong(v);
return;
case Array:
this.encodeArray(v);
return;
case Map:
this.encodeMap(v);
return;
case Date:
this.encodeDateTime(v);
return;
case Uint8Array:
this.encodeBinary(v);
return;
default:
if (typeof v !== 'object') {
this.encodeNull();
return;
}
this.encodeObject(v);
return;
}
}
getBytes(clone = false) {
return this.dw.getBytes(clone);
}
}
export class BonDecoder {
constructor() {
this.dr = new DataReader(new Uint8Array(0));
this.strArr = [];
}
reset(bytes) {
this.dr.reset(bytes);
this.strArr.length = 0;
}
decode() {
const tag = this.dr.readUInt8();
switch (tag) {
default:
return null;
case 1:
return this.dr.readInt32();
case 2:
return this.dr.readInt64();
case 3:
return this.dr.readFloat32();
case 4:
return this.dr.readFloat64();
case 5: {
const s = this.dr.readUTF();
this.strArr.push(s);
return s;
}
case 6:
return this.dr.readUInt8() === 1;
case 7: {
const len = this.dr.read7BitInt();
return this.dr.readUint8Array(len, false);
}
case 8: {
const count = this.dr.read7BitInt();
const obj = {};
for (let i = 0; i < count; i++) {
const k = this.decode();
const v = this.decode();
obj[k] = v;
}
return obj;
}
case 9: {
const len = this.dr.read7BitInt();
const arr = new Array(len);
for (let i = 0; i < len; i++) arr[i] = this.decode();
return arr;
}
case 10:
return new Date(this.dr.readInt64());
case 99:
return this.strArr[this.dr.read7BitInt()];
}
}
}
// 单例实例
const _enc = new BonEncoder();
const _dec = new BonDecoder();
// BON 编解码函数
export const bon = {
encode: (value, clone = true) => {
_enc.reset();
_enc.encode(value);
return _enc.getBytes(clone);
},
decode: (bytes) => {
_dec.reset(bytes);
return _dec.decode();
}
};
/** —— 协议消息包装,与原 ProtoMsg 类等价 —— */
export class ProtoMsg {
constructor(raw) {
if (raw?.cmd) {
raw.cmd = raw.cmd.toLowerCase();
}
this._raw = raw;
this._rawData = undefined;
this._data = undefined;
this._t = undefined;
this._sendMsg = undefined;
this.rtt = 0;
}
get sendMsg() { return this._sendMsg; }
get seq() { return this._raw.seq; }
get resp() { return this._raw.resp; }
get ack() { return this._raw.ack; }
get cmd() { return this._raw?.cmd && this._raw?.cmd.toLowerCase(); }
get code() { return ~~this._raw.code; }
get error() { return this._raw.error; }
get time() { return this._raw.time; }
get body() { return this._raw.body; }
/** 惰性 decode body → rawDatabon.decode */
get rawData() {
if (this._rawData !== undefined || this.body === undefined) return this._rawData;
this._rawData = bon.decode(this.body);
return this._rawData;
}
/** 指定数据类型 */
setDataType(t) {
if (t) this._t = { name: t.name ?? 'Anonymous', ctor: t };
return this;
}
/** 配置"请求"对象,让 respType 自动对齐 */
setSendMsg(msg) {
this._sendMsg = msg;
return this.setDataType(msg.respType);
}
/** 将 rawData 反序列化为业务对象 */
getData(clazz) {
if (this._data !== undefined || this.rawData === undefined) return this._data;
let t = this._t;
if (clazz && t && clazz !== t.ctor) {
console.warn(`getData type not match, ${clazz.name} != ${t.name}`);
t = { name: clazz.name, ctor: clazz };
}
this._data = this.rawData;
return this._data;
}
toLogString() {
const e = { ...this._raw };
delete e.body;
e.data = this.rawData;
e.rtt = this.rtt;
return JSON.stringify(e);
}
}
/** —— 加解密器注册表 —— */
const registry = new Map();
/** lz4 + 头部掩码的 "lx" 方案 */
const lx = {
encrypt: (buf) => {
let e = lz4.compress(buf);
const t = 2 + ~~(Math.random() * 248);
for (let n = Math.min(e.length, 100); --n >= 0; ) e[n] ^= t;
// 写入标识与混淆位
e[0] = 112; e[1] = 108;
e[2] = (e[2] & 0b10101010) | ((t >> 7 & 1) << 6) | ((t >> 6 & 1) << 4) | ((t >> 5 & 1) << 2) | (t >> 4 & 1);
e[3] = (e[3] & 0b10101010) | ((t >> 3 & 1) << 6) | ((t >> 2 & 1) << 4) | ((t >> 1 & 1) << 2) | (t & 1);
return e;
},
decrypt: (e) => {
const t =
((e[2] >> 6 & 1) << 7) | ((e[2] >> 4 & 1) << 6) | ((e[2] >> 2 & 1) << 5) | ((e[2] & 1) << 4) |
((e[3] >> 6 & 1) << 3) | ((e[3] >> 4 & 1) << 2) | ((e[3] >> 2 & 1) << 1) | (e[3] & 1);
for (let n = Math.min(100, e.length); --n >= 2; ) e[n] ^= t;
e[0] = 4; e[1] = 34; e[2] = 77; e[3] = 24; // 还原头以便 lz4 解
return lz4.decompress(e);
}
};
/** 随机首 4 字节 + XOR 的 "x" 方案 */
const x = {
encrypt: (e) => {
const rnd = ~~(Math.random() * 0xFFFFFFFF) >>> 0;
const n = new Uint8Array(e.length + 4);
n[0] = rnd & 0xFF; n[1] = (rnd >>> 8) & 0xFF; n[2] = (rnd >>> 16) & 0xFF; n[3] = (rnd >>> 24) & 0xFF;
n.set(e, 4);
const r = 2 + ~~(Math.random() * 248);
for (let i = n.length; --i >= 0; ) n[i] ^= r;
n[0] = 112; n[1] = 120;
n[2] = (n[2] & 0b10101010) | ((r >> 7 & 1) << 6) | ((r >> 6 & 1) << 4) | ((r >> 5 & 1) << 2) | (r >> 4 & 1);
n[3] = (n[3] & 0b10101010) | ((r >> 3 & 1) << 6) | ((r >> 2 & 1) << 4) | ((r >> 1 & 1) << 2) | (r & 1);
return n;
},
decrypt: (e) => {
const t =
((e[2] >> 6 & 1) << 7) | ((e[2] >> 4 & 1) << 6) | ((e[2] >> 2 & 1) << 5) | ((e[2] & 1) << 4) |
((e[3] >> 6 & 1) << 3) | ((e[3] >> 4 & 1) << 2) | ((e[3] >> 2 & 1) << 1) | (e[3] & 1);
for (let n = e.length; --n >= 4; ) e[n] ^= t;
return e.subarray(4);
}
};
/** 依赖 globalThis.XXTEA 的 "xtm" 方案 */
const xtm = {
encrypt: (e) => globalThis.XXTEA ? globalThis.XXTEA.encryptMod({ data: e.buffer, length: e.length }) : e,
decrypt: (e) => globalThis.XXTEA ? globalThis.XXTEA.decryptMod({ data: e.buffer, length: e.length }) : e,
};
/** 注册器 */
function register(name, impl) {
registry.set(name, impl);
}
register('lx', lx);
register('x', x);
register('xtm', xtm);
/** 默认使用 x 加密(自动检测解密) */
const passthrough = {
encrypt: (e) => getEnc('x').encrypt(e),
decrypt: (e) => {
if (e.length > 4 && e[0] === 112 && e[1] === 108) e = getEnc('lx').decrypt(e);
else if (e.length > 4 && e[0] === 112 && e[1] === 120) e = getEnc('x').decrypt(e);
else if (e.length > 3 && e[0] === 112 && e[1] === 116) e = getEnc('xtm').decrypt(e);
return e;
}
};
/** 对外:按名称取加解密器;找不到则用默认 */
export function getEnc(name) {
return registry.get(name) ?? passthrough;
}
/** 对外encodebon.encode → 加密) */
export function encode(obj, enc) {
let bytes = bon.encode(obj, false);
const out = enc.encrypt(bytes);
return out.buffer.byteLength === out.length ? out.buffer : out.buffer.slice(0, out.length);
}
/** 对外parse解密 → bon.decode → ProtoMsg */
export function parse(buf, enc) {
const u8 = new Uint8Array(buf);
const plain = enc.decrypt(u8);
const raw = bon.decode(plain);
return new ProtoMsg(raw);
}
// 游戏消息模板
export const GameMessages = {
// 心跳消息
heartBeat: (ack = 0, seq = 0) => ({
ack,
body: undefined,
c: undefined,
cmd: "_sys/ack",
hint: undefined,
seq,
time: Date.now()
}),
// 获取角色信息
getRoleInfo: (ack = 0, seq = 0, params = {}) => ({
cmd: "role_getroleinfo",
body: encode({
clientVersion: "1.65.3-wx",
inviteUid: 0,
platform: "hortor",
platformExt: "mix",
scene: "",
...params
}, getEnc('x')),
ack: ack || 0,
seq: seq || 0,
time: Date.now()
}),
// 获取数据包版本
getDataBundleVer: (ack = 0, seq = 0, params = {}) => ({
cmd: "system_getdatabundlever",
body: encode({
isAudit: false,
...params
}, getEnc('x')),
ack: ack || 0,
seq: seq || 0,
time: Date.now()
}),
// 购买金币
buyGold: (ack = 0, seq = 0, params = {}) => ({
ack,
body: encode({
buyNum: 1,
...params
}, getEnc('x')),
cmd: "system_buygold",
seq,
time: Date.now()
}),
// 签到奖励
signInReward: (ack = 0, seq = 0, params = {}) => ({
ack,
body: encode({
...params
}, getEnc('x')),
cmd: "system_signinreward",
seq,
time: Date.now()
}),
// 领取每日任务奖励
claimDailyReward: (ack = 0, seq = 0, params = {}) => ({
ack,
body: encode({
rewardId: 0,
...params
}, getEnc('x')),
cmd: "task_claimdailyreward",
seq,
time: Date.now()
})
};
// 创建全局实例
export const g_utils = {
getEnc,
encode: (obj, encName = 'x') => encode(obj, getEnc(encName)),
parse: (data, encName = 'auto') => parse(data, getEnc(encName)),
bon // 添加BON编解码器
};
// 兼容性导出(保持旧的接口)
export const bonProtocol = {
encode: bon.encode,
decode: bon.decode,
createMessage: (cmd, body = {}, ack = 0, seq = 0, options = {}) => ({
cmd,
body: bon.encode(body),
ack: ack || 0,
seq: seq || 0,
time: Date.now(),
...options
}),
parseMessage: (messageData) => {
try {
let message;
if (typeof messageData === 'string') {
message = JSON.parse(messageData);
} else {
message = messageData;
}
if (message.body && (message.body instanceof ArrayBuffer || message.body instanceof Uint8Array)) {
message.body = bon.decode(message.body);
}
return message;
} catch (error) {
console.error('消息解析失败:', error);
return {
error: true,
message: '消息解析失败',
originalData: messageData
};
}
},
generateSeq: () => Math.floor(Math.random() * 1000000),
generateMessageId: () => 'msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
};
// 导出单独的加密器类以兼容测试文件
export const LXCrypto = lx;
export const XCrypto = x;
export const XTMCrypto = xtm;
export default { ProtoMsg, getEnc, encode, parse, GameMessages, g_utils, bon, bonProtocol };