1mod imp {
10 use crate::{Result, YamlDocument};
11 use std::collections::HashMap;
12 use std::fmt;
13 use std::path::{Path, PathBuf};
14 use std::sync::{Arc, Mutex, RwLock};
15 use unity_asset_binary::asset::SerializedFile;
16 use unity_asset_binary::bundle::AssetBundle;
17 use unity_asset_binary::file::{UnityFile, load_unity_file, load_unity_file_from_shared_range};
18 use unity_asset_binary::object::{ObjectHandle, UnityObject};
19 use unity_asset_binary::typetree::TypeTreeRegistry;
20 use unity_asset_binary::typetree::{
21 TypeTreeParseMode, TypeTreeParseOptions, TypeTreeParseWarning,
22 };
23 use unity_asset_binary::webfile::WebFile;
24 use unity_asset_core::UnityValue;
25 use unity_asset_core::{UnityAssetError, UnityClass, UnityDocument};
26
27 mod container;
28 mod key;
29 mod loader;
30 mod object_query;
31 mod pptr;
32 mod stream;
33
34 #[derive(Debug, Clone)]
35 pub enum EnvironmentWarning {
36 LoadFailed {
37 path: PathBuf,
38 error: String,
39 },
40 YamlDocumentSkipped {
41 path: PathBuf,
42 doc_index: usize,
43 error: String,
44 },
45 }
46
47 impl fmt::Display for EnvironmentWarning {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 match self {
50 EnvironmentWarning::LoadFailed { path, error } => {
51 write!(f, "Failed to load {}: {}", path.to_string_lossy(), error)
52 }
53 EnvironmentWarning::YamlDocumentSkipped {
54 path,
55 doc_index,
56 error,
57 } => write!(
58 f,
59 "YAML warning in {} (doc {}): {}",
60 path.to_string_lossy(),
61 doc_index,
62 error
63 ),
64 }
65 }
66 }
67
68 pub trait EnvironmentReporter: Send + Sync {
69 fn warn(&self, warning: &EnvironmentWarning);
70 fn typetree_warning(&self, _key: &BinaryObjectKey, _warning: &TypeTreeParseWarning) {}
71 }
72
73 #[derive(Debug, Default)]
74 pub struct NoopReporter;
75
76 impl EnvironmentReporter for NoopReporter {
77 fn warn(&self, _warning: &EnvironmentWarning) {}
78 }
79
80 #[derive(Debug, Clone, Copy)]
81 pub struct EnvironmentOptions {
82 pub typetree: TypeTreeParseOptions,
83 }
84
85 impl EnvironmentOptions {
86 pub fn strict() -> Self {
87 Self {
88 typetree: TypeTreeParseOptions {
89 mode: TypeTreeParseMode::Strict,
90 },
91 }
92 }
93
94 pub fn lenient() -> Self {
95 Self {
96 typetree: TypeTreeParseOptions {
97 mode: TypeTreeParseMode::Lenient,
98 },
99 }
100 }
101 }
102
103 impl Default for EnvironmentOptions {
104 fn default() -> Self {
105 Self::lenient()
106 }
107 }
108
109 #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
110 pub enum BinarySource {
111 Path(PathBuf),
112 WebEntry {
113 web_path: PathBuf,
114 entry_name: String,
115 },
116 }
117
118 impl fmt::Display for BinarySource {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 match self {
121 BinarySource::Path(p) => write!(f, "{}", p.to_string_lossy()),
122 BinarySource::WebEntry {
123 web_path,
124 entry_name,
125 } => write!(f, "{}::{}", web_path.to_string_lossy(), entry_name),
126 }
127 }
128 }
129
130 impl BinarySource {
131 pub fn path<P: AsRef<Path>>(path: P) -> Self {
132 Self::Path(path.as_ref().to_path_buf())
133 }
134
135 pub fn describe(&self) -> String {
136 self.to_string()
137 }
138
139 fn as_path(&self) -> Option<&PathBuf> {
140 match self {
141 BinarySource::Path(p) => Some(p),
142 BinarySource::WebEntry { .. } => None,
143 }
144 }
145 }
146
147 #[derive(Clone)]
152 pub struct BinaryObjectRef<'a> {
153 pub source: &'a BinarySource,
154 pub source_kind: BinarySourceKind,
155 pub asset_index: Option<usize>,
157 pub object: ObjectHandle<'a>,
158 typetree_options: TypeTreeParseOptions,
159 reporter: Option<Arc<dyn EnvironmentReporter>>,
160 }
161
162 impl<'a> fmt::Debug for BinaryObjectRef<'a> {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 f.debug_struct("BinaryObjectRef")
165 .field("source", &self.source)
166 .field("source_kind", &self.source_kind)
167 .field("asset_index", &self.asset_index)
168 .field("path_id", &self.object.path_id())
169 .finish()
170 }
171 }
172
173 impl<'a> BinaryObjectRef<'a> {
174 pub fn read(&self) -> Result<UnityObject> {
175 let obj = self
176 .object
177 .read_with_options(self.typetree_options)
178 .map_err(|e| {
179 UnityAssetError::format(format!("Failed to parse binary object: {}", e))
180 })?;
181
182 if let Some(reporter) = &self.reporter {
183 let key = self.key();
184 for w in obj.typetree_warnings() {
185 reporter.typetree_warning(&key, w);
186 }
187 }
188
189 Ok(obj)
190 }
191
192 pub fn key(&self) -> BinaryObjectKey {
194 BinaryObjectKey {
195 source: self.source.clone(),
196 source_kind: self.source_kind,
197 asset_index: self.asset_index,
198 path_id: self.object.path_id(),
199 }
200 }
201 }
202
203 #[derive(Debug, Clone)]
205 pub enum EnvironmentObjectRef<'a> {
206 Yaml(&'a UnityClass),
207 Binary(BinaryObjectRef<'a>),
208 }
209
210 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
211 pub enum BinarySourceKind {
212 SerializedFile,
213 AssetBundle,
214 }
215
216 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
221 pub struct BinaryObjectKey {
222 pub source: BinarySource,
223 pub source_kind: BinarySourceKind,
224 pub asset_index: Option<usize>,
225 pub path_id: i64,
226 }
227
228 #[derive(Debug, Clone, PartialEq, Eq)]
230 pub struct BundleContainerEntry {
231 pub bundle_source: BinarySource,
232 pub asset_index: usize,
233 pub asset_path: String,
234 pub file_id: i32,
235 pub path_id: i64,
236 pub key: Option<BinaryObjectKey>,
237 }
238
239 pub struct Environment {
241 yaml_documents: HashMap<PathBuf, YamlDocument>,
243 binary_assets: HashMap<BinarySource, SerializedFile>,
245 bundles: HashMap<BinarySource, AssetBundle>,
247 webfiles: HashMap<PathBuf, WebFile>,
248 bundle_container_cache: RwLock<HashMap<BinarySource, Vec<BundleContainerEntry>>>,
249 warnings: Mutex<Vec<EnvironmentWarning>>,
250 reporter: Option<Arc<dyn EnvironmentReporter>>,
251 options: EnvironmentOptions,
252 type_tree_registry: Option<Arc<dyn TypeTreeRegistry>>,
253 #[allow(dead_code)]
255 base_path: PathBuf,
256 }
257
258 impl Environment {
259 pub fn new() -> Self {
261 Self::with_options(EnvironmentOptions::default())
262 }
263
264 pub fn with_options(options: EnvironmentOptions) -> Self {
265 Self {
266 yaml_documents: HashMap::new(),
267 binary_assets: HashMap::new(),
268 bundles: HashMap::new(),
269 webfiles: HashMap::new(),
270 bundle_container_cache: RwLock::new(HashMap::new()),
271 warnings: Mutex::new(Vec::new()),
272 reporter: None,
273 options,
274 type_tree_registry: None,
275 base_path: std::env::current_dir().unwrap_or_default(),
276 }
277 }
278
279 pub fn set_reporter(&mut self, reporter: Option<Arc<dyn EnvironmentReporter>>) {
280 self.reporter = reporter;
281 }
282
283 pub fn set_type_tree_registry(&mut self, registry: Option<Arc<dyn TypeTreeRegistry>>) {
284 self.type_tree_registry = registry.clone();
285
286 for file in self.binary_assets.values_mut() {
287 file.set_type_tree_registry(registry.clone());
288 }
289 for bundle in self.bundles.values_mut() {
290 for file in bundle.assets.iter_mut() {
291 file.set_type_tree_registry(registry.clone());
292 }
293 }
294 }
295
296 pub fn options(&self) -> EnvironmentOptions {
297 self.options
298 }
299
300 pub fn warnings(&self) -> Vec<EnvironmentWarning> {
301 match self.warnings.lock() {
302 Ok(v) => v.clone(),
303 Err(e) => e.into_inner().clone(),
304 }
305 }
306
307 pub fn take_warnings(&self) -> Vec<EnvironmentWarning> {
308 match self.warnings.lock() {
309 Ok(mut v) => std::mem::take(&mut *v),
310 Err(e) => {
311 let mut v = e.into_inner();
312 std::mem::take(&mut *v)
313 }
314 }
315 }
316
317 fn push_warning(&self, warning: EnvironmentWarning) {
318 match self.warnings.lock() {
319 Ok(mut warnings) => warnings.push(warning.clone()),
320 Err(e) => e.into_inner().push(warning.clone()),
321 }
322 if let Some(reporter) = &self.reporter {
323 reporter.warn(&warning);
324 }
325 }
326
327 pub fn yaml_objects(&self) -> impl Iterator<Item = &UnityClass> {
329 self.yaml_documents.values().flat_map(|doc| doc.entries())
330 }
331
332 pub fn find_yaml_by_anchor(&self, anchor: &str) -> Option<&UnityClass> {
334 self.yaml_objects().find(|obj| obj.anchor == anchor)
335 }
336
337 pub fn objects(&self) -> Box<dyn Iterator<Item = EnvironmentObjectRef<'_>> + '_> {
339 let yaml_iter = self.yaml_objects().map(EnvironmentObjectRef::Yaml);
340 let bin_iter = self.binary_object_infos().map(EnvironmentObjectRef::Binary);
341 Box::new(yaml_iter.chain(bin_iter))
342 }
343
344 pub fn binary_objects(&self) -> impl Iterator<Item = Result<UnityObject>> + '_ {
346 self.binary_object_infos().map(|r| r.read())
347 }
348
349 pub fn filter_by_class(&self, class_name: &str) -> Vec<&UnityClass> {
351 self.yaml_objects()
352 .filter(|obj| obj.class_name == class_name)
353 .collect()
354 }
355
356 pub fn yaml_documents(&self) -> &HashMap<PathBuf, YamlDocument> {
358 &self.yaml_documents
359 }
360
361 pub fn binary_assets(&self) -> &HashMap<BinarySource, SerializedFile> {
363 &self.binary_assets
364 }
365
366 pub fn bundles(&self) -> &HashMap<BinarySource, AssetBundle> {
368 &self.bundles
369 }
370
371 pub fn webfiles(&self) -> &HashMap<PathBuf, WebFile> {
373 &self.webfiles
374 }
375 }
376
377 impl Default for Environment {
378 fn default() -> Self {
379 Self::new()
380 }
381 }
382
383 #[cfg(test)]
384 mod tests;
385}
386
387pub use imp::*;