use std::borrow::Cow;
use super::{BuildTarget, SerializedFileCommonHeader, COMMON_STRING};
use super::{Serialized, SerializedFileFormatVersion};
use crate::type_tree::{reader::TypeTreeObjectBinReadClassArgs, TypeField};
use crate::until::{binrw_parser::*, Endian};
use binrw::{binrw, NullString};
use binrw::{io::Cursor, BinRead};
use std::fmt;
use std::io::{prelude::*, SeekFrom};
use std::sync::Arc;
#[binrw]
#[brw(big)]
#[derive(Debug, PartialEq)]
pub struct SerializedFile {
header: SerializedFileCommonHeader,
endianess: Endian,
reserved: [u8; 3],
#[br(is_little = endianess == Endian::Little)]
content: SerializedFileContent,
}
impl Serialized for SerializedFile {
fn get_serialized_file_version(&self) -> &SerializedFileFormatVersion {
&self.header.version
}
fn get_data_offset(&self) -> u64 {
self.header.data_offset as u64
}
fn get_endianess(&self) -> &Endian {
&self.endianess
}
fn get_objects_metadata(&self) -> Vec<super::Object> {
self.content
.objects
.iter()
.map(|obj| super::Object {
path_id: obj.path_id,
byte_start: obj.byte_start as u64,
byte_size: obj.byte_size,
class: self
.content
.types
.get(obj.type_id as usize)
.map(|t| t.class_id)
.unwrap_or(0),
type_id: obj.type_id as usize,
})
.collect()
}
fn get_unity_version(&self) -> String {
self.content.unity_version.to_string()
}
fn get_target_platform(&self) -> &BuildTarget {
&self.content.target_platform
}
fn get_enable_type_tree(&self) -> bool {
*self.content.enable_type_tree
}
fn get_type_object_args_by_type_id(
&self,
type_id: usize,
) -> Option<TypeTreeObjectBinReadClassArgs> {
let stypetree = &self.content.types.get(type_id)?;
let type_tree = stypetree.type_tree.as_ref()?;
let mut type_fields = Vec::new();
let mut string_reader = Cursor::new(&type_tree.string_buffer);
for tp in &type_tree.type_tree_node_blobs {
type_fields.push(Arc::new(Box::new(TypeTreeNode {
name: tp.get_name_str(&mut string_reader),
type_name: tp.get_type_str(&mut string_reader),
node: tp.clone(),
}) as Box<dyn TypeField + Send + Sync>))
}
Some(TypeTreeObjectBinReadClassArgs::new(
stypetree.class_id,
type_fields,
))
}
fn get_externals(&self) -> Cow<Vec<FileIdentifier>> {
return Cow::Borrowed(&self.content.externals);
}
}
#[binrw]
#[derive(Debug, PartialEq)]
struct SerializedFileContent {
unity_version: NullString,
target_platform: BuildTarget,
enable_type_tree: U8Bool,
type_count: u32,
#[br(args { count: type_count as usize, inner: SerializedTypeBinReadArgs { enable_type_tree:*enable_type_tree } })]
types: Vec<SerializedType>,
object_count: i32,
#[br(count = object_count)]
objects: Vec<Object>,
script_count: i32,
#[br(count = script_count)]
script_types: Vec<ScriptType>,
externals_count: i32,
#[br(count = externals_count)]
externals: Vec<FileIdentifier>,
user_information: NullString,
}
#[binrw]
#[br(import { enable_type_tree: bool})]
#[derive(Debug, PartialEq)]
pub struct SerializedType {
class_id: i32,
is_stripped_type: U8Bool,
script_type_index: i16,
#[br(if(class_id == 114))]
script_id: Option<[u8; 16]>,
old_type_hash: [u8; 16],
#[br(if(enable_type_tree))]
type_tree: Option<TypeTree>,
}
#[binrw]
#[derive(Clone, PartialEq)]
pub struct TypeTree {
number_of_nodes: i32,
string_buffer_size: i32,
#[br(count = number_of_nodes)]
pub type_tree_node_blobs: Vec<TypeTreeNodeBlob>,
#[br(count = string_buffer_size)]
pub string_buffer: Vec<u8>,
}
impl fmt::Debug for TypeTree {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut string_reader = Cursor::new(&self.string_buffer);
write!(f, "TypeTree [")?;
if f.alternate() {
writeln!(f)?;
}
for node in &self.type_tree_node_blobs {
write!(
f,
"{:?} -> {{ type: {}, name: {} }},",
node,
node.get_type_str(&mut string_reader),
node.get_name_str(&mut string_reader)
)?;
if f.alternate() {
writeln!(f)?;
}
}
write!(f, "]")
}
}
#[binrw]
#[derive(Debug, Clone, PartialEq)]
pub struct TypeTreeNodeBlob {
version: u16,
level: u8,
type_flags: u8,
type_str_offset: u32,
name_str_offset: u32,
byte_size: i32,
index: i32,
meta_flag: i32,
}
impl TypeTreeNodeBlob {
pub fn get_type_str<R: Read + Seek>(&self, reader: &mut R) -> String {
read_type_tree_string(self.type_str_offset, reader)
}
pub fn get_name_str<R: Read + Seek>(&self, reader: &mut R) -> String {
read_type_tree_string(self.name_str_offset, reader)
}
}
pub fn read_type_tree_string<R: Read + Seek>(value: u32, reader: &mut R) -> String {
let is_offset = (value & 0x80000000) == 0;
if is_offset {
if reader.seek(SeekFrom::Start(value.into())).is_ok() {
return NullString::read(reader)
.unwrap_or(NullString::default())
.to_string();
} else {
return String::default();
}
}
let offset = value & 0x7FFFFFFF;
COMMON_STRING.get(&offset).unwrap_or(&"").to_string()
}
#[binrw]
#[derive(Debug, PartialEq)]
pub struct Object {
#[br(align_before(4))]
pub path_id: i64,
pub byte_start: u32,
pub byte_size: u32,
pub type_id: i32,
}
#[binrw]
#[derive(Debug, PartialEq)]
pub struct ScriptType {
local_serialized_file_index: i32,
#[br(align_before(4))]
local_identifier_in_file: i64,
}
#[binrw]
#[derive(Debug, PartialEq, Clone)]
pub struct FileIdentifier {
pub temp_empty: NullString,
pub guid: [u8; 16],
pub r#type: i32,
pub path: NullString,
}
#[derive(Debug, PartialEq)]
pub struct TypeTreeNode {
pub name: String,
pub type_name: String,
pub node: TypeTreeNodeBlob,
}
impl TypeField for TypeTreeNode {
fn get_version(&self) -> u16 {
self.node.version
}
fn get_level(&self) -> u8 {
self.node.level
}
fn is_array(&self) -> bool {
self.node.type_flags & 1 > 0
}
fn get_byte_size(&self) -> i32 {
self.node.byte_size
}
fn get_index(&self) -> i32 {
self.node.index
}
fn get_meta_flag(&self) -> i32 {
self.node.meta_flag
}
fn is_align(&self) -> bool {
self.node.meta_flag & 0x4000 > 0
}
fn get_ref_type_hash(&self) -> Option<u64> {
None
}
fn get_type(&self) -> &String {
&self.type_name
}
fn get_name(&self) -> &String {
&self.name
}
}