use crate::error::{BinaryError, Result};
use crate::reader::BinaryReader;
use crate::typetree::{TypeTree, TypeTreeParser};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializedType {
pub class_id: i32,
pub is_stripped_type: bool,
pub script_type_index: i16,
pub type_tree: TypeTree,
pub script_id: [u8; 16],
pub old_type_hash: [u8; 16],
pub type_dependencies: Vec<i32>,
pub class_name: String,
pub namespace: String,
pub assembly_name: String,
}
impl SerializedType {
pub fn new(class_id: i32) -> Self {
Self {
class_id,
is_stripped_type: false,
script_type_index: -1,
type_tree: TypeTree::new(),
script_id: [0; 16],
old_type_hash: [0; 16],
type_dependencies: Vec::new(),
class_name: String::new(),
namespace: String::new(),
assembly_name: String::new(),
}
}
pub fn from_reader(
reader: &mut BinaryReader,
version: u32,
enable_type_tree: bool,
is_ref_type: bool,
) -> Result<Self> {
let class_id = reader.read_i32()?;
let mut serialized_type = Self::new(class_id);
if version >= 16 {
serialized_type.is_stripped_type = reader.read_bool()?;
}
if version >= 17 {
serialized_type.script_type_index = reader.read_i16()?;
}
if version >= 13 {
let should_read_script_id = (is_ref_type && serialized_type.script_type_index >= 0)
|| (version < 16 && class_id < 0)
|| (version >= 16 && class_id == 114);
if should_read_script_id {
let script_id_bytes = reader.read_bytes(16)?;
serialized_type.script_id.copy_from_slice(&script_id_bytes);
}
let old_type_hash_bytes = reader.read_bytes(16)?;
serialized_type
.old_type_hash
.copy_from_slice(&old_type_hash_bytes);
}
if enable_type_tree {
if version >= 12 || version == 10 {
serialized_type.type_tree = TypeTreeParser::from_reader_blob(reader, version)?;
} else {
serialized_type.type_tree = TypeTreeParser::from_reader(reader, version)?;
}
if version >= 21 {
if is_ref_type {
serialized_type.class_name = reader.read_cstring()?;
serialized_type.namespace = reader.read_cstring()?;
serialized_type.assembly_name = reader.read_cstring()?;
} else {
serialized_type.type_dependencies = read_i32_array(reader)?;
}
}
}
Ok(serialized_type)
}
pub fn is_script_type(&self) -> bool {
self.class_id == 114 || self.script_type_index >= 0
}
pub fn has_type_tree(&self) -> bool {
!self.type_tree.is_empty()
}
pub fn type_name(&self) -> String {
if !self.class_name.is_empty() {
self.class_name.clone()
} else {
format!("Class_{}", self.class_id)
}
}
pub fn full_type_name(&self) -> String {
if !self.namespace.is_empty() {
format!("{}.{}", self.namespace, self.type_name())
} else {
self.type_name()
}
}
pub fn validate(&self) -> Result<()> {
if self.class_id == 0 {
return Err(BinaryError::invalid_data("Class ID cannot be zero"));
}
if self.is_script_type() && self.script_id == [0; 16] {
return Err(BinaryError::invalid_data(
"Script type must have valid script ID",
));
}
Ok(())
}
}
fn read_i32_array(reader: &mut BinaryReader) -> Result<Vec<i32>> {
let count = reader.read_i32()?;
if count < 0 {
return Err(BinaryError::invalid_data(format!(
"Negative array length: {}",
count
)));
}
let count = count as usize;
let mut values = Vec::with_capacity(count);
for _ in 0..count {
values.push(reader.read_i32()?);
}
Ok(values)
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FileIdentifier {
pub temp_empty: String,
pub guid: [u8; 16],
pub type_: i32,
pub path: String,
}
impl FileIdentifier {
pub fn from_reader(reader: &mut BinaryReader, version: u32) -> Result<Self> {
let temp_empty = if version >= 6 {
reader.read_cstring()?
} else {
String::new()
};
let mut guid = [0u8; 16];
let mut type_ = 0i32;
if version >= 5 {
let guid_bytes = reader.read_bytes(16)?;
guid.copy_from_slice(&guid_bytes);
type_ = reader.read_i32()?;
}
let path = reader.read_cstring()?;
Ok(Self {
temp_empty,
guid,
type_,
path,
})
}
pub fn new(guid: [u8; 16], type_: i32, path: String) -> Self {
Self {
temp_empty: String::new(),
guid,
type_,
path,
}
}
pub fn is_valid(&self) -> bool {
self.guid != [0; 16] || !self.path.is_empty()
}
pub fn guid_string(&self) -> String {
format!(
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
self.guid[0],
self.guid[1],
self.guid[2],
self.guid[3],
self.guid[4],
self.guid[5],
self.guid[6],
self.guid[7],
self.guid[8],
self.guid[9],
self.guid[10],
self.guid[11],
self.guid[12],
self.guid[13],
self.guid[14],
self.guid[15]
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObjectInfo {
pub path_id: i64,
pub byte_start: u64,
pub byte_size: u32,
pub type_id: i32,
pub type_index: i32,
pub data: Vec<u8>,
}
impl ObjectInfo {
pub fn new(
path_id: i64,
byte_start: u64,
byte_size: u32,
type_id: i32,
type_index: i32,
) -> Self {
Self {
path_id,
byte_start,
byte_size,
type_id,
type_index,
data: Vec::new(),
}
}
pub fn has_data(&self) -> bool {
!self.data.is_empty()
}
pub fn byte_end(&self) -> u64 {
self.byte_start + self.byte_size as u64
}
pub fn validate(&self) -> Result<()> {
if self.path_id == 0 {
return Err(BinaryError::invalid_data("Path ID cannot be zero"));
}
if self.byte_size == 0 {
return Err(BinaryError::invalid_data("Byte size cannot be zero"));
}
if self.type_id == 0 {
return Err(BinaryError::invalid_data("Type ID cannot be zero"));
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct TypeRegistry {
types: HashMap<i32, SerializedType>,
script_types: HashMap<i16, SerializedType>,
}
impl TypeRegistry {
pub fn new() -> Self {
Self {
types: HashMap::new(),
script_types: HashMap::new(),
}
}
pub fn add_type(&mut self, serialized_type: SerializedType) {
let class_id = serialized_type.class_id;
if serialized_type.script_type_index >= 0 {
self.script_types
.insert(serialized_type.script_type_index, serialized_type.clone());
}
self.types.insert(class_id, serialized_type);
}
pub fn get_type(&self, class_id: i32) -> Option<&SerializedType> {
self.types.get(&class_id)
}
pub fn get_script_type(&self, script_index: i16) -> Option<&SerializedType> {
self.script_types.get(&script_index)
}
pub fn class_ids(&self) -> Vec<i32> {
self.types.keys().copied().collect()
}
pub fn script_indices(&self) -> Vec<i16> {
self.script_types.keys().copied().collect()
}
pub fn has_type(&self, class_id: i32) -> bool {
self.types.contains_key(&class_id)
}
pub fn has_script_type(&self, script_index: i16) -> bool {
self.script_types.contains_key(&script_index)
}
pub fn len(&self) -> usize {
self.types.len()
}
pub fn is_empty(&self) -> bool {
self.types.is_empty()
}
pub fn clear(&mut self) {
self.types.clear();
self.script_types.clear();
}
pub fn find_types<F>(&self, predicate: F) -> Vec<&SerializedType>
where
F: Fn(&SerializedType) -> bool,
{
self.types.values().filter(|t| predicate(t)).collect()
}
pub fn script_types(&self) -> Vec<&SerializedType> {
self.script_types.values().collect()
}
pub fn non_script_types(&self) -> Vec<&SerializedType> {
self.types
.values()
.filter(|t| !t.is_script_type())
.collect()
}
}
pub use unity_asset_core::class_ids;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LocalSerializedObjectIdentifier {
pub local_serialized_file_index: i32,
pub local_identifier_in_file: i64,
}
impl LocalSerializedObjectIdentifier {
pub fn from_reader(reader: &mut BinaryReader, version: u32) -> Result<Self> {
let local_serialized_file_index = reader.read_i32()?;
let local_identifier_in_file = if version < 14 {
reader.read_i32()? as i64
} else {
reader.align()?;
reader.read_i64()?
};
Ok(Self {
local_serialized_file_index,
local_identifier_in_file,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serialized_type_creation() {
let stype = SerializedType::new(114);
assert_eq!(stype.class_id, 114);
assert!(stype.is_script_type());
}
#[test]
fn test_file_identifier_guid() {
let guid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let file_id = FileIdentifier::new(guid, 0, "test.unity".to_string());
let guid_str = file_id.guid_string();
assert!(guid_str.contains("01020304"));
}
#[test]
fn test_type_registry() {
let mut registry = TypeRegistry::new();
let stype = SerializedType::new(28);
registry.add_type(stype);
assert!(registry.has_type(28));
assert_eq!(registry.len(), 1);
}
}