Phân Tích Đơn Giản Về Định Dạng Gói Tài Nguyên Unity3D - nói dối e blog

Phân Tích Đơn Giản Về Định Dạng Gói Tài Nguyên Unity3D

Unity3D có một định dạng gói tài nguyên (asset bundle) không được công bố chính thức. Tuy nhiên, để thực hiện các bản cập nhật hiệu quả hơn thông qua việc phân tích sự khác biệt, việc hiểu rõ cấu trúc đóng gói này là điều cần thiết. Điều này cho phép phát triển các công cụ so sánh và hợp nhất dữ liệu chuyên dụng, vượt trội hơn nhiều so với phương pháp so sánh nhị phân thông thường. Nhờ việc phân tách dữ liệu trong asset bundle thành các đơn vị độc lập, chúng ta có thể tập trung vào việc so sánh các đơn vị thay đổi thay vì toàn bộ dữ liệu.

Các tài liệu phi chính thức trên mạng thường trích dẫn công cụ mã nguồn mở disunity - một dự án viết bằng Java. Mặc dù không cung cấp tài liệu phân tích định dạng chi tiết, công cụ này đã được cộng đồng phát triển dựa trên việc nghiên cứu mã nguồn. Qua việc phân tích disunity, chúng tôi ghi nhận các đặc điểm chính sau:

Asset bundle tồn tại ở hai chế độ: nén và không nén. Chế độ nén sử dụng thư viện LZMA mã nguồn mở để nén toàn bộ gói dữ liệu chưa nén. Phần tiêu đề nén gồm 13 byte với cấu trúc đặc biệt: 5 byte đầu chứa tham số cấu hình (props) cần thiết cho giải thuật giải nén LZMA, 4 byte tiếp theo ghi nhận độ dài dữ liệu sau khi giải nén, và 4 byte cuối được bỏ qua. Khi giải nén dữ liệu, định dạng sẽ trở về trạng thái không nén, cho phép phân tích tiếp theo.

Cấu trúc tiêu đề file cơ bản bao gồm:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
struct AssetBundleFileHead {
   struct LevelInfo {
     unsigned int PackSize;        // Kích thước khi nén
     unsigned int UncompressedSize; // Kích thước thực tế
   };
   string     FileID;              //  định danh file
   unsigned int   Version;         // Phiên bản định dạng
   string     MainVersion;         // Phiên bản chính hệ thống
   string     BuildVersion;        // Thông tin build cụ thể
   size_t     MinimumStreamedBytes; // Dữ liệu tối thiểu cần tải
   size_t     HeaderSize;          // Kích thước tiêu đề
   size_t     NumberOfLevelsToDownloadBeforeStreaming; // Cấp độ cần tải trước
   size_t     LevelCount;          // Tổng số cấp độ
   LevelInfo   LevelList[];        // Danh sách thông tin cấp độ
   size_t     CompleteFileSize;    // Kích thước hoàn chỉnh file
   size_t     FileInfoHeaderSize;  // Kích thước tiêu đề thông tin file
   bool     Compressed;            // Trạng thái nén
};

Định dạng chuỗi sử dụng ký tự kết thúc \0, các giá trị size_t được lưu theo định dạng big-endian 4 byte, trong khi kiểu bool chỉ chiếm 1 byte. Quan trọng cần lưu ý rằng cấu trúc này có sự khác biệt tùy theo phiên bản Unity. Phiên bản 3 được sử dụng phổ biến từ Unity 3.5 đến 4.x, vì vậy tài liệu tập trung phân tích phiên bản này.

Mỗi asset bundle chứa nhiều file tài nguyên được đóng gói theo cấu trúc:

1
2
3
4
5
6
7
8
9
struct AssetFileHeader {
   struct AssetFileInfo {
     string name;         // Tên file
     size_t offset;       // Vị trí bắt đầu trong gói
     size_t length;       // Độ dài dữ liệu
   };
   size_t FileCount;      // Số lượng file trong gói
   AssetFileInfo   File[]; // Danh sách thông tin file
};

Điểm mấu chốt nằm ở cách tính toán offset: giá trị này được tính từ vị trí sau HeaderSize. Điều này cho phép xác định chính xác vị trí từng phần dữ liệu trong file tổng thể bằng phép tính “HeaderSize + offset riêng phần”.

Với mỗi asset, cấu trúc dữ liệu phức tạp hơn khi bao gồm các thành phần TypeTree, ObjectPath và AssetRef. Phiên bản hiện tại sử dụng định dạng số 9 (Format=9) với sự khác biệt đáng kể về thứ tự byte so với các phiên bản trước. Cấu trúc AssetHeader cơ bản:

1
2
3
4
5
6
7
struct AssetHeader {
   size_t TypeTreeSize;    // Kích thước TypeTree
   size_t FileSize;        // Kích thước toàn bộ dữ liệu
   unsigned int Format;    // Phiên bản định dạng
   size_t dataOffset;      // Vị trí dữ liệu
   size_t Unknown;         // Giá trị chưa xác định
};

Quan trọng cần lưu ý rằng TypeTree - thành phần mô tả cấu trúc dữ liệu, có thể bị lược bỏ trong quá trình build cho thiết bị di động để tối ưu kích thước. Mỗi đối tượng asset có class ID riêng, cho phép xác định cách giải nén dữ liệu tương ứng. Mặc dù không cần phân tích chi tiết từng thuộc tính đối tượng để thực hiện so sánh cấp độ đối tượng, nhưng việc hiểu cơ bản về cơ chế này vẫn rất hữu ích.

Cấu trúc ObjectHeader quản lý các đối tượng bên trong asset:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct ObjectHeader {
   struct ObjectInfo {
     int pathID;            // Mã định danh đường dẫn
     int offset;            // Vị trí tương đối
     int length;            // Độ dài dữ liệu
     byte classID[8];       // Mã định danh lớp đối tượng
   };
   int ObjectCount;         // Tổng số đối tượng
   ObjectInfo Object[];     // Danh sách thông tin đối tượng
};

Đặc biệt, các giá trị kiểu int trong phần này sử dụng định dạng little-endian, khác biệt so với định dạng big-endian ở cấp độ file tổng thể. PathID thực chất là giá trị băm của chuỗi đường dẫn đầy đủ, đóng vai trò như khóa tra cứu đối tượng.

Thành phần AssetRefTable theo sau ObjectHeader chứa thông tin tham chiếu đến asset bên ngoài:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct AssetTable {
   struct AssetRef {
     byte GUID[8];         // Mã định danh toàn cục
     int type;             // Loại tài nguyên
     string filePath;      // Đường dẫn file
     string assetPath;     // Đường dẫn tài nguyên cụ thể
   };
   int Count;              // Số lượng tham chiếu
   byte Unknown;           // Giá trị chưa xác định
  vector Refs;             // Danh sách tham chiếu
};

Hiểu biết về cấu trúc phức tạp này không chỉ giúp tối ưu hóa quy trình cập nhật mà còn mở ra khả năng phân tích và tối ưu hóa hiệu năng hệ thống tài nguyên trong Unity3D. Việc nắm rõ các cơ chế đóng gói và giải nén này là nền tảng cho việc phát triển các công cụ quản lý tài nguyên chuyên nghiệp và hiệu quả.

0%