use std::io;
use crate::assembly::AssemblyTypeTreeGenerator;
use crate::template::*;
use dotnetdll::prelude::Resolution;
use dotnetdll::resolved::Accessibility as Access;
use dotnetdll::resolved::attribute::Attribute;
use dotnetdll::resolved::members::{Accessibility, Field, MethodReferenceParent, UserMethod};
use dotnetdll::resolved::types::{
BaseType, Kind, MemberType, MethodType, ResolutionScope, TypeDefinition, TypeImplementation,
TypeSource, UserType,
};
use rabex::UnityVersion;
const BASE_STOP: &[&str] = &[
"System.Object",
"UnityEngine.Object",
"UnityEngine.MonoBehaviour",
"UnityEngine.ScriptableObject",
];
type Resolved = (
&'static Resolution<'static>,
&'static TypeDefinition<'static>,
);
#[derive(Clone)]
struct GenericArg {
member: &'static MemberType,
ctx: TypeCtx,
}
#[derive(Clone)]
struct TypeCtx {
res: &'static Resolution<'static>,
args: std::rc::Rc<[GenericArg]>,
}
impl TypeCtx {
fn root(res: &'static Resolution<'static>) -> Self {
TypeCtx {
res,
args: std::rc::Rc::from([]),
}
}
}
struct ResolvedType {
res: &'static Resolution<'static>,
def: &'static TypeDefinition<'static>,
ctx: TypeCtx,
}
impl ResolvedType {
fn ctx(&self) -> TypeCtx {
self.ctx.clone()
}
}
fn effective(member: &'static MemberType, ctx: &TypeCtx) -> (&'static MemberType, TypeCtx) {
if let MemberType::TypeGeneric(n) = member
&& let Some(arg) = ctx.args.get(*n)
{
return effective(arg.member, &arg.ctx);
}
(member, ctx.clone())
}
pub(crate) struct Generator<'r> {
assemblies: &'r AssemblyTypeTreeGenerator,
version: &'r UnityVersion,
loader: &'r crate::assembly::Loader<'r>,
using_managed_reference: bool,
}
impl<'r> Generator<'r> {
pub(crate) fn new(
assemblies: &'r AssemblyTypeTreeGenerator,
version: &'r UnityVersion,
loader: &'r crate::assembly::Loader<'r>,
) -> Self {
Generator {
assemblies,
version,
loader,
using_managed_reference: false,
}
}
pub(crate) fn read(
&mut self,
primary: &'static Resolution<'static>,
namespace: &str,
type_name: &str,
) -> Result<Option<Vec<TemplateField>>, io::Error> {
let Some((res, def)) = self.find_type_following_forwards(primary, namespace, type_name)?
else {
return Ok(None);
};
let mut children = Vec::new();
let limit = serialization_limit(self.version);
self.recursive_type_load(&TypeCtx::root(res), def, &mut children, limit, true)?;
if self.using_managed_reference {
children.push(managed_references_registry("references", self.version));
}
Ok(Some(children))
}
fn recursive_type_load(
&mut self,
ctx: &TypeCtx,
def: &'static TypeDefinition<'static>,
out: &mut Vec<TemplateField>,
available_depth: i32,
is_recursive_call: bool,
) -> Result<(), io::Error> {
let depth = if is_recursive_call {
available_depth
} else {
available_depth - 1
};
if let Some(base) = self.inherited_base(ctx, def)? {
self.recursive_type_load(&base.ctx(), base.def, out, depth, true)?;
}
out.extend(self.read_types(ctx, def, depth)?);
Ok(())
}
fn inherited_base(
&self,
ctx: &TypeCtx,
def: &'static TypeDefinition<'static>,
) -> Result<Option<ResolvedType>, io::Error> {
let Some(ts) = def.extends.as_ref() else {
return Ok(None);
};
if BASE_STOP.contains(&source_type_name(ts, ctx.res).as_str()) {
return Ok(None);
}
self.resolve_source_in(ts, ctx)
}
fn read_types(
&mut self,
ctx: &TypeCtx,
def: &'static TypeDefinition<'static>,
available_depth: i32,
) -> Result<Vec<TemplateField>, io::Error> {
let mut out = Vec::new();
for fi in self.acceptable_fields(ctx, def, available_depth)? {
let field = &def.fields[fi];
let (member, ectx) = effective(&field.return_type, ctx);
let mut element = member;
let mut element_ctx = ectx.clone();
let mut is_array_or_list = false;
if let Some(elem) = vector_element(member) {
is_array_or_list = true;
element = elem;
} else if let Some(elem) = list_element(member, ectx.res) {
is_array_or_list = true;
element = elem;
}
if is_array_or_list {
(element, element_ctx) = effective(element, &ectx);
}
let kind = self.classify(field, element, &element_ctx, ctx.res, available_depth)?;
let ty = kind.type_name();
let plain_vector = kind.collection_is_plain_vector();
let mut node = TemplateField {
name: field.name.to_string(),
aligned: type_aligns_by_name(&ty),
ty,
children: kind.into_children(self.version),
};
if is_array_or_list {
node = if plain_vector {
vector(node)
} else {
vector_with_type(node)
};
}
out.push(node);
}
Ok(out)
}
fn classify(
&mut self,
field: &Field,
element: &'static MemberType,
element_ctx: &TypeCtx,
field_res: &'static Resolution<'static>,
available_depth: i32,
) -> Result<FieldKind, io::Error> {
if let Some(name) = base_primitive_name(element) {
return Ok(FieldKind::Primitive(name.to_string()));
}
if is_string(element) {
return Ok(FieldKind::String);
}
let Some(rt) = self.resolve_concrete(element, element_ctx)? else {
return Ok(FieldKind::Primitive(String::new()));
};
if let Some(under) = self.enum_underlying(rt.res, rt.def) {
let ty = convert_base_to_primitive(&under).unwrap_or("int");
return Ok(FieldKind::Primitive(ty.to_string()));
}
if self.derives_from_ueobject(rt.res, rt.def)? {
return Ok(FieldKind::PPtr(rt.def.name.to_string()));
}
if field_has_serialize_reference(field, field_res) {
self.using_managed_reference = true;
return Ok(FieldKind::ManagedReference);
}
let def = rt.def;
let full_name = def.type_name();
let children = if is_special_unity_type(&full_name) {
match special_unity_children(&def.name, self.version) {
Some(children) => children,
None => self.serialized(rt, available_depth)?,
}
} else if def.flags.serializable {
self.serialized(rt, available_depth)?
} else {
Vec::new()
};
Ok(FieldKind::Named {
ty: def.name.to_string(),
children,
})
}
fn serialized(
&mut self,
rt: ResolvedType,
available_depth: i32,
) -> Result<Vec<TemplateField>, io::Error> {
let mut out = Vec::new();
self.recursive_type_load(&rt.ctx(), rt.def, &mut out, available_depth, false)?;
Ok(out)
}
fn acceptable_fields(
&self,
ctx: &TypeCtx,
def: &'static TypeDefinition<'static>,
available_depth: i32,
) -> Result<Vec<usize>, io::Error> {
let mut valid = Vec::new();
for (fi, field) in def.fields.iter().enumerate() {
let is_public = matches!(field.accessibility, Accessibility::Access(Access::Public));
let has_serialize_attr = field_has_attr(field, ctx.res, "UnityEngine.SerializeField")
|| field_has_attr(field, ctx.res, "UnityEngine.SerializeReference");
if !(is_public || has_serialize_attr) {
continue;
}
if field.static_member || field.not_serialized || field.init_only || field.literal {
continue;
}
let (member, ectx) = effective(&field.return_type, ctx);
let (check, check_ctx) = if let Some(elem) = collection_element(member, ectx.res) {
if available_depth < 0 {
continue;
}
let (elem, eectx) = effective(elem, &ectx);
if collection_element(elem, eectx.res).is_some() {
continue; }
(elem, eectx)
} else {
if self.member_is_same_type(member, &ectx, def)?
&& !self.derives_from_ueobject(ctx.res, def)?
{
continue; }
(member, ectx.clone())
};
if self.is_valid_def(field, check, &check_ctx, ctx.res, available_depth)? {
valid.push(fi);
}
}
Ok(valid)
}
fn is_valid_def(
&self,
field: &Field,
member: &'static MemberType,
member_ctx: &TypeCtx,
field_res: &'static Resolution<'static>,
available_depth: i32,
) -> Result<bool, io::Error> {
if base_primitive_name(member).is_some() || is_string(member) {
return Ok(true);
}
let Some(rt) = self.resolve_concrete(member, member_ctx)? else {
return Ok(false);
};
if !rt.def.generic_parameters.is_empty() && self.version.major < 2020 {
return Ok(false);
}
if let Some(under) = self.enum_underlying(rt.res, rt.def) {
return Ok(under != "System.Int64" && under != "System.UInt64");
}
let full_name = rt.def.type_name();
if available_depth < 0 {
return Ok(is_value_type(rt.res, rt.def)
&& (rt.def.flags.serializable || is_special_unity_type(&full_name)));
}
if self.derives_from_ueobject(rt.res, rt.def)? || is_special_unity_type(&full_name) {
return Ok(true);
}
if field_has_serialize_reference(field, field_res) {
return Ok(!is_value_type(rt.res, rt.def) && rt.def.generic_parameters.is_empty());
}
if is_assembly_blacklisted(assembly_name(rt.res)) {
return Ok(false);
}
Ok(!rt.def.flags.abstract_type && rt.def.flags.serializable)
}
fn member_is_same_type(
&self,
member: &'static MemberType,
ctx: &TypeCtx,
def: &'static TypeDefinition<'static>,
) -> Result<bool, io::Error> {
Ok(matches!(self.resolve_member(member, ctx)?, Some(rt) if std::ptr::eq(rt.def, def)))
}
pub(crate) fn derives_from_monobehaviour(
&self,
res: &'static Resolution<'static>,
def: &'static TypeDefinition<'static>,
) -> Result<bool, io::Error> {
if matches!(def.flags.kind, Kind::Interface) {
return Ok(false);
}
let Some(ts) = def.extends.as_ref() else {
return Ok(false);
};
let base = source_type_name(ts, res);
if base == "UnityEngine.MonoBehaviour" {
return Ok(true);
}
if BASE_STOP.contains(&base.as_str()) {
return Ok(false);
}
match self.resolve_source_in(ts, &TypeCtx::root(res))? {
Some(base) => self.derives_from_monobehaviour(base.res, base.def),
None => Ok(false),
}
}
fn derives_from_ueobject(
&self,
res: &'static Resolution<'static>,
def: &'static TypeDefinition<'static>,
) -> Result<bool, io::Error> {
if matches!(def.flags.kind, Kind::Interface) {
return Ok(false);
}
if def.type_name() == "UnityEngine.Object" {
return Ok(true);
}
let Some(ts) = def.extends.as_ref() else {
return Ok(false);
};
let base = source_type_name(ts, res);
if base == "UnityEngine.Object" {
return Ok(true);
}
if base == "System.Object" {
return Ok(false);
}
match self.resolve_source_in(ts, &TypeCtx::root(res))? {
Some(base) => self.derives_from_ueobject(base.res, base.def),
None => Ok(false),
}
}
fn enum_underlying(
&self,
res: &'static Resolution<'static>,
def: &'static TypeDefinition<'static>,
) -> Option<String> {
if base_full_name(res, def).as_deref() != Some("System.Enum") {
return None;
}
let value_field = def.fields.iter().find(|f| f.name == "value__")?;
base_primitive_full_name(&value_field.return_type)
}
fn resolve_member(
&self,
member: &'static MemberType,
ctx: &TypeCtx,
) -> Result<Option<ResolvedType>, io::Error> {
let (member, ctx) = effective(member, ctx);
self.resolve_concrete(member, &ctx)
}
fn resolve_concrete(
&self,
member: &'static MemberType,
ctx: &TypeCtx,
) -> Result<Option<ResolvedType>, io::Error> {
let MemberType::Base(b) = member else {
return Ok(None);
};
match &**b {
BaseType::Type { source, .. } => self.resolve_source_in(source, ctx),
_ => Ok(None),
}
}
fn resolve_source_in(
&self,
source: &'static TypeSource<MemberType>,
ctx: &TypeCtx,
) -> Result<Option<ResolvedType>, io::Error> {
let (user, parameters) = match source {
TypeSource::User(user) => (user, [].as_slice()),
TypeSource::Generic { base, parameters } => (base, parameters.as_slice()),
};
let Some((res, def)) = self.resolve_user(user, ctx.res)? else {
return Ok(None);
};
let args: Vec<GenericArg> = parameters
.iter()
.map(|param| {
let (member, arg_ctx) = effective(param, ctx);
GenericArg {
member,
ctx: arg_ctx,
}
})
.collect();
Ok(Some(ResolvedType {
res,
def,
ctx: TypeCtx {
res,
args: args.into(),
},
}))
}
fn resolve_user(
&self,
user: &UserType,
res: &'static Resolution<'static>,
) -> Result<Option<Resolved>, io::Error> {
match user {
UserType::Definition(idx) => Ok(Some((res, &res[*idx]))),
UserType::Reference(idx) => {
let type_ref = &res[*idx];
let namespace = type_ref.namespace.as_deref().unwrap_or("");
match &type_ref.scope {
ResolutionScope::Assembly(assembly) => {
let name = format!("{}.dll", res[*assembly].name);
let Some(target) = self.resolution(&name)? else {
return Ok(None);
};
self.find_type_following_forwards(target, namespace, &type_ref.name)
}
ResolutionScope::CurrentModule => {
self.find_type_following_forwards(res, namespace, &type_ref.name)
}
ResolutionScope::Nested(encloser_ref) => {
let encloser = UserType::Reference(*encloser_ref);
let Some((encloser_res, encloser_def)) =
self.resolve_user(&encloser, res)?
else {
return Ok(None);
};
Ok(find_nested_type(encloser_res, encloser_def, &type_ref.name)
.map(|def| (encloser_res, def)))
}
_ => Ok(None),
}
}
}
}
fn resolution(&self, name: &str) -> Result<Option<&'static Resolution<'static>>, io::Error> {
self.assemblies.resolution(name, self.loader)
}
fn find_type_following_forwards(
&self,
res: &'static Resolution<'static>,
namespace: &str,
name: &str,
) -> Result<Option<Resolved>, io::Error> {
if let Some(def) = find_type(res, namespace, name) {
return Ok(Some((res, def)));
}
let Some(exported) = res
.exported_types
.iter()
.find(|e| e.name == name && e.namespace.as_deref().unwrap_or("") == namespace)
else {
return Ok(None);
};
if let TypeImplementation::TypeForwarder(assembly) = &exported.implementation {
let Some(target) = self.resolution(&format!("{}.dll", res[*assembly].name))? else {
return Ok(None);
};
return self.find_type_following_forwards(target, namespace, name);
}
Ok(None)
}
}
enum FieldKind {
Primitive(String),
String,
PPtr(String),
ManagedReference,
Named {
ty: String,
children: Vec<TemplateField>,
},
}
impl FieldKind {
fn type_name(&self) -> String {
match self {
FieldKind::Primitive(ty) => ty.clone(),
FieldKind::String => "string".to_string(),
FieldKind::PPtr(name) => format!("PPtr<${name}>"),
FieldKind::ManagedReference => "managedReference".to_string(),
FieldKind::Named { ty, .. } => ty.clone(),
}
}
fn into_children(self, version: &UnityVersion) -> Vec<TemplateField> {
match self {
FieldKind::Primitive(_) => Vec::new(),
FieldKind::String => string_children(),
FieldKind::PPtr(_) => pptr_children(version),
FieldKind::ManagedReference => managed_reference_children(version),
FieldKind::Named { children, .. } => children,
}
}
fn collection_is_plain_vector(&self) -> bool {
matches!(
self,
FieldKind::Primitive(_) | FieldKind::String | FieldKind::PPtr(_)
)
}
}
fn find_type(
res: &'static Resolution<'static>,
namespace: &str,
type_name: &str,
) -> Option<&'static TypeDefinition<'static>> {
res.type_definitions.iter().find(|td| {
td.encloser.is_none()
&& td.name == type_name
&& td.namespace.as_deref().unwrap_or("") == namespace
})
}
fn find_nested_type(
res: &'static Resolution<'static>,
encloser: &'static TypeDefinition<'static>,
name: &str,
) -> Option<&'static TypeDefinition<'static>> {
let encloser_ptr = std::ptr::from_ref(encloser);
res.type_definitions.iter().find(|td| {
td.name == name
&& td
.encloser
.is_some_and(|idx| std::ptr::from_ref(&res[idx]) == encloser_ptr)
})
}
fn source_type_name(ts: &TypeSource<MemberType>, res: &Resolution) -> String {
match ts {
TypeSource::User(user) => user.type_name(res),
TypeSource::Generic { base, .. } => base.type_name(res),
}
}
fn base_full_name(res: &Resolution, def: &TypeDefinition) -> Option<String> {
def.extends.as_ref().map(|ts| source_type_name(ts, res))
}
fn is_value_type(res: &Resolution, def: &TypeDefinition) -> bool {
matches!(
base_full_name(res, def).as_deref(),
Some("System.ValueType") | Some("System.Enum")
)
}
fn assembly_name<'a>(res: &'a Resolution<'a>) -> &'a str {
res.assembly.as_ref().map(|a| a.name.as_ref()).unwrap_or("")
}
fn field_has_serialize_reference(field: &Field, res: &Resolution) -> bool {
field_has_attr(field, res, "UnityEngine.SerializeReference")
}
fn field_has_attr(field: &Field, res: &Resolution, full_name: &str) -> bool {
field
.attributes
.iter()
.any(|a| attribute_type_name(a, res).as_deref() == Some(full_name))
}
fn attribute_type_name(attr: &Attribute, res: &Resolution) -> Option<String> {
match attr.constructor {
UserMethod::Definition(m) => Some(res[m.parent_type()].type_name()),
UserMethod::Reference(r) => match &res[r].parent {
MethodReferenceParent::Type(mt) => method_type_name(mt, res),
_ => None,
},
}
}
fn collection_element<'m>(member: &'m MemberType, res: &Resolution) -> Option<&'m MemberType> {
vector_element(member).or_else(|| list_element(member, res))
}
fn vector_element(member: &MemberType) -> Option<&MemberType> {
if let MemberType::Base(b) = member
&& let BaseType::Vector(_, elem) = &**b
{
return Some(elem);
}
None
}
fn list_element<'m>(member: &'m MemberType, res: &Resolution) -> Option<&'m MemberType> {
if let MemberType::Base(b) = member
&& let BaseType::Type {
source: TypeSource::Generic { base, parameters },
..
} = &**b
&& base.type_name(res) == "System.Collections.Generic.List`1"
{
return parameters.first();
}
None
}
fn is_string(member: &MemberType) -> bool {
matches!(member, MemberType::Base(b) if matches!(&**b, BaseType::String))
}
fn base_primitive_name(member: &MemberType) -> Option<&'static str> {
if let MemberType::Base(b) = member {
return base_type_primitive_name(b);
}
None
}
fn base_type_primitive_name(b: &BaseType<MemberType>) -> Option<&'static str> {
Some(match b {
BaseType::Boolean => "UInt8",
BaseType::Char => "UInt16",
BaseType::Int8 => "SInt8",
BaseType::UInt8 => "UInt8",
BaseType::Int16 => "SInt16",
BaseType::UInt16 => "UInt16",
BaseType::Int32 => "int",
BaseType::UInt32 => "unsigned int",
BaseType::Int64 => "SInt64",
BaseType::UInt64 => "UInt64",
BaseType::Float32 => "float",
BaseType::Float64 => "double",
_ => return None,
})
}
fn base_primitive_full_name(member: &MemberType) -> Option<String> {
let MemberType::Base(b) = member else {
return None;
};
Some(
match &**b {
BaseType::Boolean => "System.Boolean",
BaseType::Char => "System.Char",
BaseType::Int8 => "System.SByte",
BaseType::UInt8 => "System.Byte",
BaseType::Int16 => "System.Int16",
BaseType::UInt16 => "System.UInt16",
BaseType::Int32 => "System.Int32",
BaseType::UInt32 => "System.UInt32",
BaseType::Int64 => "System.Int64",
BaseType::UInt64 => "System.UInt64",
BaseType::Float32 => "System.Single",
BaseType::Float64 => "System.Double",
_ => return None,
}
.to_string(),
)
}
fn method_type_name(mt: &MethodType, res: &Resolution) -> Option<String> {
if let MethodType::Base(b) = mt
&& let BaseType::Type { source, .. } = &**b
{
return Some(match source {
TypeSource::User(user) => user.type_name(res),
TypeSource::Generic { base, .. } => base.type_name(res),
});
}
None
}
fn serialization_limit(version: &UnityVersion) -> i32 {
if version.major > 2020
|| (version.major == 2020
&& (version.minor > 1 || (version.minor == 1 && version.build >= 4)))
|| (version.major == 2019 && version.minor == 4 && version.build >= 9)
{
10
} else {
7
}
}