diff --git a/package.json b/package.json index 24195c8..662dbf3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kunpocc", - "version": "1.0.22", + "version": "1.0.23", "type": "module", "description": "基于creator3.0+的kunpocc库", "main": "./dist/kunpocc.cjs", diff --git a/src/kunpocc.ts b/src/kunpocc.ts index 7b71987..4a479a8 100644 --- a/src/kunpocc.ts +++ b/src/kunpocc.ts @@ -6,6 +6,7 @@ export { Platform, PlatformType } from "./global/Platform"; export { Screen } from "./global/Screen"; /** tool */ +export { Binary } from "./tool/Binary"; export * from "./tool/log"; export { MathTool } from "./tool/Math"; export { md5 } from "./tool/MD5"; diff --git a/src/tool/Binary.ts b/src/tool/Binary.ts new file mode 100644 index 0000000..5e40bef --- /dev/null +++ b/src/tool/Binary.ts @@ -0,0 +1,267 @@ +/** + * @Author: Gongxh + * @Date: 2025-03-04 + * @Description: 二进制工具类 - 使用 JavaScript 标准库实现 + */ + +export class Binary { + /** + * 将对象转换为二进制数据 + */ + public static toBinary(obj: any): Uint8Array { + // console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); + // console.log("原始数据", JSON.stringify(obj)); + const chunks: Uint8Array[] = []; + this.writeValue(obj, chunks); + + // 计算总长度 + const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + const result = new Uint8Array(totalLength); + + // 合并所有数据块 + let offset = 0; + for (const chunk of chunks) { + result.set(chunk, offset); + offset += chunk.length; + } + // console.log("二进制数据", result); + // console.log("还原数据", JSON.stringify(this.toJson(result))); + return result; + } + + /** + * 将二进制数据转换JSON数据 + * @param binary 二进制数据 + * @returns + */ + public static toJson(binary: any): any { + // 如果是 ArrayBuffer, 转换为 Uint8Array + const uint8Array = binary instanceof ArrayBuffer ? new Uint8Array(binary) : binary; + // 检查是否为二进制格式 + if (!this.isBinaryFormat(uint8Array)) { + // 如果不是二进制格式, 直接返回 + return binary; + } + const view = new DataView(uint8Array.buffer); + let offset = 0; + return this.readValue(view, offset); + } + + /** + * 检查数据是否为二进制格式 + * @param data 要检查的数据 + * @returns 是否为二进制格式 + */ + public static isBinaryFormat(data: Uint8Array): boolean { + if (!data || !data.length || data.length < 1) { + return false; + } + // 检查第一个字节是否为有效的类型标记(0-5) + const firstByte = data[0]; + if (firstByte < 0 || firstByte > 5) { + return false + } + + // 检查数据格式是否符合我们的二进制格式规范 + try { + const view = new DataView(data.buffer); + let offset = 0; + + // 递归检查数据格式 + this.validateBinaryFormat(view, offset); + return true; + } catch (error) { + return false; + } + } + + /** + * 验证二进制数据格式 + * @param view DataView对象 + * @param offset 当前偏移量 + * @returns 下一个数据的偏移量 + */ + private static validateBinaryFormat(view: DataView, offset: number): number { + const type = view.getUint8(offset); + + switch (type) { + case 0: // null + return 1; + case 1: // number + return 9; + case 2: { // string + const strLen = view.getUint32(offset + 1, true); + return 5 + strLen; + } + case 3: // boolean + return 2; + case 4: { // array + const arrLen = view.getUint32(offset + 1, true); + let size = 5; + for (let i = 0; i < arrLen; i++) { + size += this.validateBinaryFormat(view, offset + size); + } + return size; + } + case 5: { // object + const objLen = view.getUint32(offset + 1, true); + let size = 5; + for (let i = 0; i < objLen; i++) { + const keyLen = view.getUint32(offset + size, true); + size += 4 + keyLen; + size += this.validateBinaryFormat(view, offset + size); + } + return size; + } + default: + throw new Error('无效的类型标记'); + } + } + + private static readValue(view: DataView, offset: number): any { + const type = view.getUint8(offset++); + + switch (type) { + case 0: // null + return null; + case 1: // number + const num = view.getFloat64(offset, true); + return num; + case 2: { // string + const strLen = view.getUint32(offset, true); + offset += 4; + const strBytes = new Uint8Array(view.buffer, offset, strLen); + return new TextDecoder().decode(strBytes); + } + case 3: // boolean + return view.getUint8(offset) === 1; + case 4: // array + const arrLen = view.getUint32(offset, true); + offset += 4; + const arr = []; + for (let i = 0; i < arrLen; i++) { + arr.push(this.readValue(view, offset)); + offset += this.getNextOffset(view, offset); + } + return arr; + case 5: { // object + const objLen = view.getUint32(offset, true); + offset += 4; + const obj: any = {}; + for (let i = 0; i < objLen; i++) { + const keyLen = view.getUint32(offset, true); + offset += 4; + let key = ''; + for (let j = 0; j < keyLen; j++) { + key += String.fromCharCode(view.getUint8(offset + j)); + } + offset += keyLen; + obj[key] = this.readValue(view, offset); + offset += this.getNextOffset(view, offset); + } + return obj; + } + default: + throw new Error(`未知的类型: ${type}`); + } + } + + private static writeValue(value: any, chunks: Uint8Array[]): void { + if (value === null) { + chunks.push(new Uint8Array([0])); + return; + } + + switch (typeof value) { + case 'number': { + const numBuf = new Uint8Array(9); + numBuf[0] = 1; + const view = new DataView(numBuf.buffer); + view.setFloat64(1, value, true); + chunks.push(numBuf); + break; + } + case 'string': { + const encoder = new TextEncoder(); + const strBytes = encoder.encode(value); + const strLen = strBytes.length; + const strBuf = new Uint8Array(5 + strLen); + strBuf[0] = 2; + const view = new DataView(strBuf.buffer); + view.setUint32(1, strLen, true); + strBuf.set(strBytes, 5); + chunks.push(strBuf); + break; + } + case 'boolean': { + const boolBuf = new Uint8Array(2); + boolBuf[0] = 3; + boolBuf[1] = value ? 1 : 0; + chunks.push(boolBuf); + break; + } + case 'object': { + if (Array.isArray(value)) { + const arrBuf = new Uint8Array(5); + arrBuf[0] = 4; + const view = new DataView(arrBuf.buffer); + view.setUint32(1, value.length, true); + chunks.push(arrBuf); + for (const item of value) { + this.writeValue(item, chunks); + } + } else { + const keys = Object.keys(value); + const objBuf = new Uint8Array(5); + objBuf[0] = 5; + const view = new DataView(objBuf.buffer); + view.setUint32(1, keys.length, true); + chunks.push(objBuf); + for (const key of keys) { + const keyLen = key.length; + const keyBuf = new Uint8Array(4 + keyLen); + const keyView = new DataView(keyBuf.buffer); + keyView.setUint32(0, keyLen, true); + const keyBytes = new TextEncoder().encode(key); + keyBuf.set(keyBytes, 4); + chunks.push(keyBuf); + this.writeValue(value[key], chunks); + } + } + break; + } + default: + throw new Error(`不支持的类型: ${typeof value}`); + } + } + + private static getNextOffset(view: DataView, offset: number): number { + const type = view.getUint8(offset); + switch (type) { + case 0: return 1; // null + case 1: return 9; // number + case 2: return 5 + view.getUint32(offset + 1, true); // string + case 3: return 2; // boolean + case 4: { // array + const arrLen = view.getUint32(offset + 1, true); + let currentSize = 5; + for (let i = 0; i < arrLen; i++) { + currentSize += this.getNextOffset(view, offset + currentSize); + } + return currentSize; + } + case 5: { // object + const objLen = view.getUint32(offset + 1, true); + let currentSize = 5; + for (let i = 0; i < objLen; i++) { + const keyLen = view.getUint32(offset + currentSize, true); + currentSize += 4 + keyLen; + currentSize += this.getNextOffset(view, offset + currentSize); + } + return currentSize; + } + default: + throw new Error(`未知的类型: ${type}`); + } + } +} \ No newline at end of file