use std::collections::{BTreeMap, HashMap};
use std::path::Path;
use std::sync::{Arc, Mutex};
use dotnetdll::prelude::{ReadOptions, Resolution};
use rabex::UnityVersion;
use crate::TypeTreeNode;
use crate::generator::Generator;
pub type Loader<'a> = dyn Fn(&str) -> Result<Vec<u8>, std::io::Error> + 'a;
pub struct AssemblyTypeTreeGenerator {
bytes: Mutex<HashMap<String, &'static [u8]>>,
resolutions: Mutex<HashMap<String, &'static Resolution<'static>>>,
locks: Mutex<HashMap<String, Arc<Mutex<()>>>>,
unity_version: UnityVersion,
}
impl AssemblyTypeTreeGenerator {
pub fn new(unity_version: UnityVersion) -> Self {
AssemblyTypeTreeGenerator {
bytes: Mutex::new(HashMap::new()),
resolutions: Mutex::new(HashMap::new()),
locks: Mutex::new(HashMap::new()),
unity_version,
}
}
fn assembly_bytes(
&self,
assembly_name: &str,
loader: &Loader,
) -> Result<Option<&'static [u8]>, std::io::Error> {
let mut bytes = self.bytes.lock().unwrap();
if let Some(b) = bytes.get(assembly_name) {
return Ok(Some(b));
}
let loaded = match loader(assembly_name) {
Ok(loaded) => loaded,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(e) => return Err(e),
};
let leaked: &'static [u8] = Vec::leak(loaded);
bytes.insert(assembly_name.to_owned(), leaked);
Ok(Some(leaked))
}
pub(crate) fn resolution(
&self,
assembly_name: &str,
loader: &Loader,
) -> Result<Option<&'static Resolution<'static>>, std::io::Error> {
if let Some(resolution) = self.resolutions.lock().unwrap().get(assembly_name) {
return Ok(Some(resolution));
}
let assembly_lock = {
let mut locks = self.locks.lock().unwrap();
Arc::clone(locks.entry(assembly_name.to_owned()).or_default())
};
let _flight = assembly_lock.lock().unwrap();
if let Some(resolution) = self.resolutions.lock().unwrap().get(assembly_name) {
return Ok(Some(resolution));
}
let Some(bytes) = self.assembly_bytes(assembly_name, loader)? else {
return Ok(None);
};
#[allow(clippy::needless_update)] let options = ReadOptions {
skip_method_bodies: true,
lazy_method_bodies: false,
lazy_method_signatures: true,
..Default::default()
};
let Ok(resolution) = Resolution::parse(bytes, options) else {
return Ok(None);
};
let leaked: &'static Resolution<'static> = Box::leak(Box::new(resolution));
self.resolutions
.lock()
.unwrap()
.insert(assembly_name.to_owned(), leaked);
Ok(Some(leaked))
}
pub fn generate(
&self,
loader: &Loader,
assembly_name: &str,
namespace: &str,
type_name: &str,
) -> Result<Option<TypeTreeNode>, std::io::Error> {
let Some(primary) = self.resolution(assembly_name, loader)? else {
return Ok(None);
};
let children = Generator::new(self, &self.unity_version, loader)
.read(primary, namespace, type_name)?;
Ok(children.map(|children| crate::assemble(children, type_name)))
}
pub fn generate_from_dir(
&self,
managed_dir: &Path,
assembly_name: &str,
namespace: &str,
type_name: &str,
) -> Result<Option<TypeTreeNode>, std::io::Error> {
self.generate(
&|name| std::fs::read(managed_dir.join(name)),
assembly_name,
namespace,
type_name,
)
}
pub fn load_assembly(&self, loader: &Loader, name: &str) -> Result<bool, std::io::Error> {
self.resolution(name, loader).map(|r| r.is_some())
}
pub fn monobehaviour_definitions(
&self,
loader: &Loader,
) -> Result<BTreeMap<String, Vec<String>>, std::io::Error> {
let g = crate::generator::Generator::new(self, &self.unity_version, loader);
let resolutions: Vec<(String, &'static Resolution<'static>)> = self
.resolutions
.lock()
.unwrap()
.iter()
.map(|(k, v)| (k.clone(), *v))
.collect();
let mut defs: BTreeMap<String, Vec<String>> = BTreeMap::new();
for (asm_name, res) in &resolutions {
for td in &res.type_definitions {
if td.encloser.is_some() {
continue;
}
if g.derives_from_monobehaviour(res, td)? {
defs.entry(asm_name.clone())
.or_default()
.push(td.type_name());
}
}
}
Ok(defs)
}
}