396 lines
9.4 KiB
TypeScript
396 lines
9.4 KiB
TypeScript
|
|
/**
|
|||
|
|
* ByteBuffer
|
|||
|
|
*
|
|||
|
|
* Binary data reader for parsing FairyGUI package files.
|
|||
|
|
*
|
|||
|
|
* 二进制数据读取器,用于解析 FairyGUI 包文件
|
|||
|
|
*/
|
|||
|
|
export class ByteBuffer {
|
|||
|
|
private _data: DataView;
|
|||
|
|
private _position: number = 0;
|
|||
|
|
private _littleEndian: boolean = false;
|
|||
|
|
private _stringTable: string[] = [];
|
|||
|
|
private _version: number = 0;
|
|||
|
|
|
|||
|
|
constructor(buffer: ArrayBuffer, offset: number = 0, length?: number) {
|
|||
|
|
length = length ?? buffer.byteLength - offset;
|
|||
|
|
this._data = new DataView(buffer, offset, length);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get buffer length
|
|||
|
|
* 获取缓冲区长度
|
|||
|
|
*/
|
|||
|
|
public get length(): number {
|
|||
|
|
return this._data.byteLength;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get current position
|
|||
|
|
* 获取当前位置
|
|||
|
|
*/
|
|||
|
|
public get position(): number {
|
|||
|
|
return this._position;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Set current position
|
|||
|
|
* 设置当前位置
|
|||
|
|
*/
|
|||
|
|
public set position(value: number) {
|
|||
|
|
this._position = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get version
|
|||
|
|
* 获取版本
|
|||
|
|
*/
|
|||
|
|
public get version(): number {
|
|||
|
|
return this._version;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Set version
|
|||
|
|
* 设置版本
|
|||
|
|
*/
|
|||
|
|
public set version(value: number) {
|
|||
|
|
this._version = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if can read more bytes
|
|||
|
|
* 检查是否可以读取更多字节
|
|||
|
|
*/
|
|||
|
|
public get bytesAvailable(): number {
|
|||
|
|
return this._data.byteLength - this._position;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Skip bytes
|
|||
|
|
* 跳过字节
|
|||
|
|
*/
|
|||
|
|
public skip(count: number): void {
|
|||
|
|
this._position += count;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Seek to position
|
|||
|
|
* 定位到指定位置
|
|||
|
|
*/
|
|||
|
|
public seek(indexTablePos: number, blockIndex: number): boolean {
|
|||
|
|
const tmp = this._position;
|
|||
|
|
this._position = indexTablePos;
|
|||
|
|
|
|||
|
|
const segCount = this.getUint8();
|
|||
|
|
if (blockIndex < segCount) {
|
|||
|
|
const useShort = this.getUint8() === 1;
|
|||
|
|
let newPos: number;
|
|||
|
|
|
|||
|
|
if (useShort) {
|
|||
|
|
this._position = indexTablePos + 2 + 2 * blockIndex;
|
|||
|
|
newPos = this.getUint16();
|
|||
|
|
} else {
|
|||
|
|
this._position = indexTablePos + 2 + 4 * blockIndex;
|
|||
|
|
newPos = this.getUint32();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (newPos > 0) {
|
|||
|
|
this._position = indexTablePos + newPos;
|
|||
|
|
return true;
|
|||
|
|
} else {
|
|||
|
|
this._position = tmp;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
this._position = tmp;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Read methods | 读取方法
|
|||
|
|
|
|||
|
|
public getUint8(): number {
|
|||
|
|
const value = this._data.getUint8(this._position);
|
|||
|
|
this._position += 1;
|
|||
|
|
return value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public getInt8(): number {
|
|||
|
|
const value = this._data.getInt8(this._position);
|
|||
|
|
this._position += 1;
|
|||
|
|
return value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public getUint16(): number {
|
|||
|
|
const value = this._data.getUint16(this._position, this._littleEndian);
|
|||
|
|
this._position += 2;
|
|||
|
|
return value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public getInt16(): number {
|
|||
|
|
const value = this._data.getInt16(this._position, this._littleEndian);
|
|||
|
|
this._position += 2;
|
|||
|
|
return value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public getUint32(): number {
|
|||
|
|
const value = this._data.getUint32(this._position, this._littleEndian);
|
|||
|
|
this._position += 4;
|
|||
|
|
return value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public getInt32(): number {
|
|||
|
|
const value = this._data.getInt32(this._position, this._littleEndian);
|
|||
|
|
this._position += 4;
|
|||
|
|
return value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public getFloat32(): number {
|
|||
|
|
const value = this._data.getFloat32(this._position, this._littleEndian);
|
|||
|
|
this._position += 4;
|
|||
|
|
return value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public getFloat64(): number {
|
|||
|
|
const value = this._data.getFloat64(this._position, this._littleEndian);
|
|||
|
|
this._position += 8;
|
|||
|
|
return value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read boolean
|
|||
|
|
* 读取布尔值
|
|||
|
|
*/
|
|||
|
|
public readBool(): boolean {
|
|||
|
|
return this.getUint8() === 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read byte
|
|||
|
|
* 读取字节
|
|||
|
|
*/
|
|||
|
|
public readByte(): number {
|
|||
|
|
return this.getUint8();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read short
|
|||
|
|
* 读取短整数
|
|||
|
|
*/
|
|||
|
|
public readShort(): number {
|
|||
|
|
return this.getInt16();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read unsigned short
|
|||
|
|
* 读取无符号短整数
|
|||
|
|
*/
|
|||
|
|
public readUshort(): number {
|
|||
|
|
return this.getUint16();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read int
|
|||
|
|
* 读取整数
|
|||
|
|
*/
|
|||
|
|
public readInt(): number {
|
|||
|
|
return this.getInt32();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read unsigned int
|
|||
|
|
* 读取无符号整数
|
|||
|
|
*/
|
|||
|
|
public readUint(): number {
|
|||
|
|
return this.getUint32();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read float
|
|||
|
|
* 读取浮点数
|
|||
|
|
*/
|
|||
|
|
public readFloat(): number {
|
|||
|
|
return this.getFloat32();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read string from string table
|
|||
|
|
* 从字符串表读取字符串
|
|||
|
|
*/
|
|||
|
|
public readS(): string {
|
|||
|
|
const index = this.getUint16();
|
|||
|
|
if (index === 65535) {
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
return this._stringTable[index] || '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read string with length prefix
|
|||
|
|
* 读取带长度前缀的字符串
|
|||
|
|
*/
|
|||
|
|
public readString(): string {
|
|||
|
|
const len = this.getUint16();
|
|||
|
|
if (len === 0) {
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
return this.readStringWithLength(len);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private readStringWithLength(len: number): string {
|
|||
|
|
const bytes = new Uint8Array(this._data.buffer, this._data.byteOffset + this._position, len);
|
|||
|
|
this._position += len;
|
|||
|
|
return new TextDecoder('utf-8').decode(bytes);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read color as packed u32 (0xRRGGBBAA format)
|
|||
|
|
* 读取颜色为打包的 u32(0xRRGGBBAA 格式)
|
|||
|
|
*/
|
|||
|
|
public readColor(bHasAlpha: boolean = false): number {
|
|||
|
|
const r = this.getUint8();
|
|||
|
|
const g = this.getUint8();
|
|||
|
|
const b = this.getUint8();
|
|||
|
|
const a = this.getUint8();
|
|||
|
|
return ((r << 24) | (g << 16) | (b << 8) | (bHasAlpha ? a : 0xFF)) >>> 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read color as CSS string (always reads 4 bytes: r, g, b, a)
|
|||
|
|
* 读取颜色为 CSS 字符串(总是读取 4 字节:r, g, b, a)
|
|||
|
|
*
|
|||
|
|
* FairyGUI 二进制格式颜色存储顺序为 R, G, B, A
|
|||
|
|
*/
|
|||
|
|
public readColorS(bHasAlpha: boolean = false): string {
|
|||
|
|
const byte0 = this.getUint8();
|
|||
|
|
const byte1 = this.getUint8();
|
|||
|
|
const byte2 = this.getUint8();
|
|||
|
|
const byte3 = this.getUint8();
|
|||
|
|
|
|||
|
|
// FairyGUI stores colors as R, G, B, A
|
|||
|
|
const r = byte0;
|
|||
|
|
const g = byte1;
|
|||
|
|
const b = byte2;
|
|||
|
|
const a = byte3;
|
|||
|
|
|
|||
|
|
if (bHasAlpha && a !== 255) {
|
|||
|
|
return `rgba(${r},${g},${b},${(a / 255).toFixed(2)})`;
|
|||
|
|
} else {
|
|||
|
|
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read bytes
|
|||
|
|
* 读取字节数组
|
|||
|
|
*/
|
|||
|
|
public readBytes(length: number): Uint8Array {
|
|||
|
|
const bytes = new Uint8Array(this._data.buffer, this._data.byteOffset + this._position, length);
|
|||
|
|
this._position += length;
|
|||
|
|
return bytes;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Set string table
|
|||
|
|
* 设置字符串表
|
|||
|
|
*/
|
|||
|
|
public set stringTable(value: string[]) {
|
|||
|
|
this._stringTable = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get string table
|
|||
|
|
* 获取字符串表
|
|||
|
|
*/
|
|||
|
|
public get stringTable(): string[] {
|
|||
|
|
return this._stringTable;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Alias for position getter
|
|||
|
|
* position getter 别名
|
|||
|
|
*/
|
|||
|
|
public get pos(): number {
|
|||
|
|
return this._position;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Alias for position setter
|
|||
|
|
* position setter 别名
|
|||
|
|
*/
|
|||
|
|
public set pos(value: number) {
|
|||
|
|
this._position = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get underlying buffer
|
|||
|
|
* 获取底层缓冲区
|
|||
|
|
*/
|
|||
|
|
public get buffer(): ArrayBuffer {
|
|||
|
|
return this._data.buffer as ArrayBuffer;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read UTF string (length-prefixed)
|
|||
|
|
* 读取 UTF 字符串(带长度前缀)
|
|||
|
|
*/
|
|||
|
|
public readUTFString(): string {
|
|||
|
|
const len = this.getUint16();
|
|||
|
|
if (len === 0) {
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
return this.readStringWithLength(len);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read string array
|
|||
|
|
* 读取字符串数组
|
|||
|
|
*/
|
|||
|
|
public readSArray(count: number): string[] {
|
|||
|
|
const arr: string[] = [];
|
|||
|
|
for (let i = 0; i < count; i++) {
|
|||
|
|
arr.push(this.readS());
|
|||
|
|
}
|
|||
|
|
return arr;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read custom string with specified length
|
|||
|
|
* 读取指定长度的自定义字符串
|
|||
|
|
*/
|
|||
|
|
public getCustomString(len: number): string {
|
|||
|
|
const bytes = new Uint8Array(this._data.buffer, this._data.byteOffset + this._position, len);
|
|||
|
|
this._position += len;
|
|||
|
|
return new TextDecoder('utf-8').decode(bytes);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read sub-buffer
|
|||
|
|
* 读取子缓冲区
|
|||
|
|
*/
|
|||
|
|
public readBuffer(): ByteBuffer {
|
|||
|
|
const len = this.getUint32();
|
|||
|
|
const buffer = new ByteBuffer(this._data.buffer as ArrayBuffer, this._data.byteOffset + this._position, len);
|
|||
|
|
buffer.version = this._version;
|
|||
|
|
buffer.stringTable = this._stringTable;
|
|||
|
|
this._position += len;
|
|||
|
|
return buffer;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read Int32 (alias)
|
|||
|
|
* 读取 Int32(别名)
|
|||
|
|
*/
|
|||
|
|
public readInt32(): number {
|
|||
|
|
return this.getInt32();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Read Uint16 (alias)
|
|||
|
|
* 读取 Uint16(别名)
|
|||
|
|
*/
|
|||
|
|
public readUint16(): number {
|
|||
|
|
return this.getUint16();
|
|||
|
|
}
|
|||
|
|
}
|