Skip to content

Commit 4256c06

Browse files
committed
feat: MonoBehaviour atlas and sprite
1 parent fd597bc commit 4256c06

13 files changed

Lines changed: 591 additions & 139 deletions

File tree

src/asset.ts

Lines changed: 20 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,31 @@
11
import type { Bundle } from './bundle';
22
import type { AssetObject } from './classes';
33
import { createAssetObject } from './classes';
4+
import { ObjectInfo } from './object';
5+
import { SerializedType } from './serializedType';
46
import { ArrayBufferReader } from './utils/reader';
57

6-
interface AssetHeader {
8+
export interface AssetHeader {
79
metadataSize: number;
810
fileSize: number;
911
version: number;
1012
dataOffset: number;
1113
endianness: number;
1214
}
1315

14-
interface TypeInfo {
15-
classId: number;
16-
}
17-
18-
export interface ObjectInfo {
19-
getReader: () => ArrayBufferReader;
20-
bundle: Bundle;
21-
buildType: string;
22-
assetVersion: number;
23-
bytesStart: number;
24-
bytesSize: number;
25-
typeId: number;
26-
classId: number;
27-
isDestroyed: number;
28-
stripped: number;
29-
pathId: bigint;
30-
version: number[];
31-
}
32-
3316
export class Asset {
34-
private readonly reader: ArrayBufferReader;
35-
private readonly header: AssetHeader;
36-
private readonly fileEndianness: number = 0;
37-
private readonly unityVersion: string = '';
38-
private readonly version: number[] = [];
39-
private readonly buildType: string = '';
40-
private readonly targetPlatform: number = 0;
41-
private readonly enableTypeTree: boolean = false;
42-
private readonly enableBigId: boolean = false;
43-
private readonly types: TypeInfo[] = [];
44-
private readonly objectInfos: ObjectInfo[] = [];
45-
private readonly cloneReader = () => this.reader.clone();
17+
readonly header: AssetHeader;
18+
readonly fileEndianness: number = 0;
19+
readonly unityVersion: string = '';
20+
readonly version: number[] = [];
21+
readonly buildType: string = '';
22+
readonly targetPlatform: number = 0;
23+
readonly enableTypeTree: boolean = false;
24+
readonly enableBigId: boolean = false;
25+
readonly types: SerializedType[] = [];
26+
readonly typeMap = new Map<number, SerializedType>();
27+
readonly objectInfos: ObjectInfo[] = [];
28+
readonly reader: ArrayBufferReader;
4629

4730
constructor(bundle: Bundle, data: ArrayBuffer) {
4831
const r = new ArrayBufferReader(data);
@@ -88,7 +71,9 @@ export class Asset {
8871

8972
const typeCount = r.readInt32();
9073
for (let i = 0; i < typeCount; i++) {
91-
this.readType(false);
74+
const type = new SerializedType(r, header, this.enableTypeTree, false);
75+
this.types.push(type);
76+
this.typeMap.set(type.classId, type);
9277
}
9378

9479
if (header.version >= 7 && header.version < 14) {
@@ -97,97 +82,13 @@ export class Asset {
9782

9883
const objectCount = r.readUInt32();
9984
for (let i = 0; i < objectCount; i++) {
100-
const info: ObjectInfo = {
101-
getReader: this.cloneReader,
102-
bundle,
103-
buildType: this.buildType,
104-
assetVersion: header.version,
105-
bytesStart: 0,
106-
bytesSize: 0,
107-
typeId: 0,
108-
classId: 0,
109-
isDestroyed: 0,
110-
stripped: 0,
111-
pathId: 0n,
112-
version: this.version,
113-
};
114-
115-
if (this.enableBigId) info.pathId = r.readInt64();
116-
else if (header.version < 14) info.pathId = BigInt(r.readInt32());
117-
else {
118-
r.align(4);
119-
info.pathId = r.readInt64();
120-
}
121-
info.bytesStart = header.version >= 22 ? Number(r.readUInt64()) : r.readUInt32();
122-
info.bytesStart += header.dataOffset;
123-
info.bytesSize = r.readUInt32();
124-
info.typeId = r.readInt32();
125-
if (header.version < 16) info.classId = r.readUInt16();
126-
else info.classId = this.types[info.typeId].classId;
127-
if (header.version < 11) info.isDestroyed = r.readUInt16();
128-
if (header.version >= 11 && header.version < 17) r.move(2);
129-
if (header.version === 15 || header.version === 16) info.stripped = r.readUInt8();
130-
131-
this.objectInfos.push(info);
85+
this.objectInfos.push(new ObjectInfo(this, bundle));
13286
}
13387

13488
// 未实现
13589
}
13690

137-
public objects() {
91+
objects() {
13892
return this.objectInfos.map(createAssetObject).filter(o => o) as AssetObject[];
13993
}
140-
141-
// 未完整实现,只用于跳过
142-
private readType(isRefType: boolean) {
143-
const r = this.reader;
144-
const { version } = this.header;
145-
146-
const info: TypeInfo = {
147-
classId: r.readInt32(),
148-
};
149-
150-
if (version >= 16) r.move(1);
151-
const scriptTypeIndex = version >= 17 ? r.readInt16() : null;
152-
if (version >= 13) {
153-
if (
154-
(isRefType && scriptTypeIndex !== null) ||
155-
(version < 16 && info.classId < 0) ||
156-
(version >= 16 && info.classId === 114)
157-
) {
158-
r.move(16);
159-
}
160-
r.move(16);
161-
}
162-
if (this.enableTypeTree) {
163-
if (version >= 12 || version === 10) this.readTypeTreeBlob();
164-
else throw new Error(`Unsupported asset version: ${version}`);
165-
if (version >= 21) {
166-
if (isRefType) {
167-
r.readStringUntilZero();
168-
r.readStringUntilZero();
169-
r.readStringUntilZero();
170-
} else {
171-
const size = r.readInt32();
172-
r.move(size * 4);
173-
}
174-
}
175-
}
176-
177-
this.types.push(info);
178-
}
179-
180-
// 未实现,只用于跳过
181-
private readTypeTreeBlob() {
182-
const r = this.reader;
183-
184-
const nodeNumber = r.readInt32();
185-
const stringBufferSize = r.readInt32();
186-
187-
for (let i = 0; i < nodeNumber; i++) {
188-
r.move(24);
189-
if (this.header.version >= 19) r.move(8);
190-
}
191-
r.move(stringBufferSize);
192-
}
19394
}

src/classes/base.ts

Lines changed: 150 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { once } from 'es-toolkit';
2+
import type { TypeTreeNode } from '../serializedType';
13
import type { ArrayBufferReader } from '../utils/reader';
24
import type { ObjectInfo } from './types';
35
import { AssetType } from './types';
@@ -37,6 +39,21 @@ const dumpObject = (obj: any): any => {
3739
return obj;
3840
};
3941

42+
const getNodes = (nodes: TypeTreeNode[], index: number): TypeTreeNode[] => {
43+
const result = [nodes[index]];
44+
const level = nodes[index].level;
45+
46+
for (let i = index + 1; i < nodes.length; i++) {
47+
const node = nodes[i];
48+
if (node.level <= level) {
49+
return result;
50+
}
51+
result.push(node);
52+
}
53+
54+
return result;
55+
};
56+
4057
export abstract class AssetBase {
4158
abstract readonly type: AssetType;
4259
readonly name: string = '';
@@ -46,7 +63,7 @@ export abstract class AssetBase {
4663
r: ArrayBufferReader,
4764
readName = true,
4865
) {
49-
r.seek(__info.bytesStart);
66+
this.getTypeTree = once(this.getTypeTree.bind(this));
5067
if (readName) this.readName(r);
5168
}
5269

@@ -81,8 +98,138 @@ export abstract class AssetBase {
8198
}
8299

83100
getRaw() {
101+
return this.__info.getReader().readBuffer(this.size);
102+
}
103+
104+
getTypeTree(): Record<string, any> {
105+
const nodes = this.__info.serializedType?.typeTree.nodes;
106+
if (!nodes) {
107+
return {};
108+
}
109+
84110
const r = this.__info.getReader();
85-
r.seek(this.__info.bytesStart);
86-
return r.readBuffer(this.size);
111+
const result: Record<string, any> = {};
112+
113+
for (let ctx = { index: 0 }; ctx.index < nodes.length; ctx.index++) {
114+
const node = nodes[ctx.index];
115+
const value = this.getTypeTreeValue(nodes, r, ctx);
116+
result[node.name] = value;
117+
}
118+
119+
return result.Base ?? result;
120+
}
121+
122+
private getTypeTreeValue(nodes: TypeTreeNode[], r: ArrayBufferReader, ctx: { index: number }) {
123+
const node = nodes[ctx.index];
124+
let align = (node.metaFlag & 0x4000) !== 0;
125+
126+
let value: any;
127+
128+
switch (node.type) {
129+
case 'SInt8':
130+
value = r.readInt8();
131+
break;
132+
case 'UInt8':
133+
case 'char':
134+
value = r.readUInt8();
135+
break;
136+
case 'short':
137+
case 'SInt16':
138+
value = r.readInt16();
139+
break;
140+
case 'UInt16':
141+
case 'unsigned short':
142+
value = r.readUInt16();
143+
break;
144+
case 'int':
145+
case 'SInt32':
146+
value = r.readInt32();
147+
break;
148+
case 'UInt32':
149+
case 'unsigned int':
150+
case 'Type*':
151+
value = r.readUInt32();
152+
break;
153+
case 'long long':
154+
case 'SInt64':
155+
value = r.readInt64();
156+
break;
157+
case 'UInt64':
158+
case 'unsigned long long':
159+
case 'FileSize':
160+
value = r.readUInt64();
161+
break;
162+
case 'float':
163+
value = r.readFloat32();
164+
break;
165+
case 'double':
166+
value = r.readFloat64();
167+
break;
168+
case 'bool':
169+
value = r.readBoolean();
170+
break;
171+
case 'string': {
172+
value = r.readAlignedString();
173+
const toSkip = getNodes(nodes, ctx.index);
174+
ctx.index += toSkip.length - 1;
175+
break;
176+
}
177+
case 'map': {
178+
if ((nodes[ctx.index + 1].metaFlag & 0x4000) !== 0) {
179+
align = true;
180+
}
181+
const size = r.readInt32();
182+
const map = getNodes(nodes, ctx.index);
183+
ctx.index += map.length - 1;
184+
const first = getNodes(map, 4);
185+
const second = getNodes(map, 4 + first.length);
186+
const mapValue: Record<string, any> = {};
187+
for (let i = 0; i < size; i++) {
188+
const key = this.getTypeTreeValue(first, r, { index: 0 });
189+
const val = this.getTypeTreeValue(second, r, { index: 0 });
190+
mapValue[key] = val;
191+
}
192+
value = mapValue;
193+
break;
194+
}
195+
case 'TypelessData': {
196+
const size = r.readInt32();
197+
const data = r.readUInt8Slice(size);
198+
ctx.index += 2;
199+
value = Array.from(data);
200+
break;
201+
}
202+
default:
203+
if (ctx.index < nodes.length - 1 && nodes[ctx.index + 1].type === 'Array') {
204+
if ((nodes[ctx.index + 1].metaFlag & 0x4000) !== 0) {
205+
align = true;
206+
}
207+
const size = r.readInt32();
208+
const vector = getNodes(nodes, ctx.index);
209+
ctx.index += vector.length - 1;
210+
const arrayValue: any[] = [];
211+
for (let i = 0; i < size; i++) {
212+
arrayValue.push(this.getTypeTreeValue(vector, r, { index: 3 }));
213+
}
214+
value = arrayValue;
215+
} else {
216+
const clz = getNodes(nodes, ctx.index);
217+
ctx.index += clz.length - 1;
218+
const classValue: Record<string, any> = {};
219+
for (let ctx2 = { index: 1 }; ctx2.index < clz.length; ctx2.index++) {
220+
const classNode = clz[ctx2.index];
221+
const val = this.getTypeTreeValue(clz, r, ctx2);
222+
classValue[classNode.name] = val;
223+
}
224+
value = classValue;
225+
}
226+
break;
227+
}
228+
229+
if (align) {
230+
r.align(4);
231+
}
232+
233+
return value;
87234
}
88235
}

0 commit comments

Comments
 (0)