Files
xyzw_web_helper/xyzw_web_helper-main开源源码更新/src/utils/bonProtocol.js
2025-10-17 20:56:50 +08:00

779 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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 };