unity_asset/environment/imp/
object_query.rs

1use super::*;
2
3impl Environment {
4    /// Iterate binary object references across all loaded bundles and standalone serialized files.
5    pub fn binary_object_infos(&self) -> impl Iterator<Item = BinaryObjectRef<'_>> {
6        let typetree_options = self.options.typetree;
7        let standalone_reporter = self.reporter.clone();
8        let bundled_reporter = self.reporter.clone();
9
10        let standalone = self.binary_assets.iter().flat_map(move |(source, file)| {
11            let reporter = standalone_reporter.clone();
12            file.object_handles().map(move |object| BinaryObjectRef {
13                source,
14                source_kind: BinarySourceKind::SerializedFile,
15                asset_index: None,
16                object,
17                typetree_options,
18                reporter: reporter.clone(),
19            })
20        });
21
22        let bundled = self
23            .bundles
24            .iter()
25            .flat_map(move |(bundle_source, bundle)| {
26                let reporter = bundled_reporter.clone();
27                bundle
28                    .assets
29                    .iter()
30                    .enumerate()
31                    .flat_map(move |(asset_index, file)| {
32                        let reporter = reporter.clone();
33                        file.object_handles().map(move |object| BinaryObjectRef {
34                            source: bundle_source,
35                            source_kind: BinarySourceKind::AssetBundle,
36                            asset_index: Some(asset_index),
37                            object,
38                            typetree_options,
39                            reporter: reporter.clone(),
40                        })
41                    })
42            });
43
44        standalone.chain(bundled)
45    }
46
47    /// List all loaded binary sources (standalone serialized files + bundles).
48    pub fn binary_sources(&self) -> Vec<(BinarySourceKind, BinarySource)> {
49        let mut out: Vec<(BinarySourceKind, BinarySource)> = Vec::new();
50
51        let mut asset_sources: Vec<BinarySource> = self.binary_assets.keys().cloned().collect();
52        asset_sources.sort();
53        out.extend(
54            asset_sources
55                .into_iter()
56                .map(|s| (BinarySourceKind::SerializedFile, s)),
57        );
58
59        let mut bundle_sources: Vec<BinarySource> = self.bundles.keys().cloned().collect();
60        bundle_sources.sort();
61        out.extend(
62            bundle_sources
63                .into_iter()
64                .map(|s| (BinarySourceKind::AssetBundle, s)),
65        );
66
67        out
68    }
69
70    /// Find binary objects by `path_id` across all loaded assets/bundles.
71    ///
72    /// Note: `path_id` is unique within a single `SerializedFile`, but not globally unique across files.
73    pub fn find_binary_objects(&self, path_id: i64) -> Vec<BinaryObjectRef<'_>> {
74        let mut out = Vec::new();
75        let typetree_options = self.options.typetree;
76        let reporter = self.reporter.clone();
77
78        let mut asset_sources: Vec<&BinarySource> = self.binary_assets.keys().collect();
79        asset_sources.sort();
80        for source in asset_sources {
81            let file = &self.binary_assets[source];
82            if let Some(object) = file.find_object_handle(path_id) {
83                out.push(BinaryObjectRef {
84                    source,
85                    source_kind: BinarySourceKind::SerializedFile,
86                    asset_index: None,
87                    object,
88                    typetree_options,
89                    reporter: reporter.clone(),
90                });
91            }
92        }
93
94        let mut bundle_sources: Vec<&BinarySource> = self.bundles.keys().collect();
95        bundle_sources.sort();
96        for bundle_source in bundle_sources {
97            let bundle = &self.bundles[bundle_source];
98            for (asset_index, asset) in bundle.assets.iter().enumerate() {
99                if let Some(object) = asset.find_object_handle(path_id) {
100                    out.push(BinaryObjectRef {
101                        source: bundle_source,
102                        source_kind: BinarySourceKind::AssetBundle,
103                        asset_index: Some(asset_index),
104                        object,
105                        typetree_options,
106                        reporter: reporter.clone(),
107                    });
108                }
109            }
110        }
111
112        out
113    }
114
115    /// Find the first matching binary object by `path_id` (best-effort).
116    pub fn find_binary_object(&self, path_id: i64) -> Option<BinaryObjectRef<'_>> {
117        self.find_binary_objects(path_id).into_iter().next()
118    }
119
120    /// Find binary objects by `path_id` within a specific loaded source (bundle path or `.assets` path).
121    pub fn find_binary_objects_in_source<P: AsRef<Path>>(
122        &self,
123        source: P,
124        path_id: i64,
125    ) -> Vec<BinaryObjectRef<'_>> {
126        let source = BinarySource::path(source.as_ref());
127        self.find_binary_objects_in_source_id(&source, path_id)
128    }
129
130    /// Find binary objects by `path_id` within a specific loaded source (including WebFile entries).
131    pub fn find_binary_objects_in_source_id(
132        &self,
133        source: &BinarySource,
134        path_id: i64,
135    ) -> Vec<BinaryObjectRef<'_>> {
136        let typetree_options = self.options.typetree;
137        let reporter = self.reporter.clone();
138
139        if let Some((key, file)) = self.binary_assets.get_key_value(source) {
140            if let Some(object) = file.find_object_handle(path_id) {
141                return vec![BinaryObjectRef {
142                    source: key,
143                    source_kind: BinarySourceKind::SerializedFile,
144                    asset_index: None,
145                    object,
146                    typetree_options,
147                    reporter,
148                }];
149            }
150            return Vec::new();
151        }
152
153        if let Some((key, bundle)) = self.bundles.get_key_value(source) {
154            let mut out = Vec::new();
155            for (asset_index, asset) in bundle.assets.iter().enumerate() {
156                if let Some(object) = asset.find_object_handle(path_id) {
157                    out.push(BinaryObjectRef {
158                        source: key,
159                        source_kind: BinarySourceKind::AssetBundle,
160                        asset_index: Some(asset_index),
161                        object,
162                        typetree_options,
163                        reporter: reporter.clone(),
164                    });
165                }
166            }
167            return out;
168        }
169
170        Vec::new()
171    }
172
173    /// Find the first binary object by `path_id` within a specific source.
174    pub fn find_binary_object_in_source<P: AsRef<Path>>(
175        &self,
176        source: P,
177        path_id: i64,
178    ) -> Option<BinaryObjectRef<'_>> {
179        self.find_binary_objects_in_source(source, path_id)
180            .into_iter()
181            .next()
182    }
183
184    pub fn find_binary_object_in_source_id(
185        &self,
186        source: &BinarySource,
187        path_id: i64,
188    ) -> Option<BinaryObjectRef<'_>> {
189        self.find_binary_objects_in_source_id(source, path_id)
190            .into_iter()
191            .next()
192    }
193
194    /// Find a binary object by `path_id` within a specific bundle + asset index.
195    pub fn find_binary_object_in_bundle_asset<P: AsRef<Path>>(
196        &self,
197        bundle_path: P,
198        asset_index: usize,
199        path_id: i64,
200    ) -> Option<BinaryObjectRef<'_>> {
201        let bundle_source = BinarySource::path(bundle_path.as_ref());
202        self.find_binary_object_in_bundle_asset_source(&bundle_source, asset_index, path_id)
203    }
204
205    pub fn find_binary_object_in_bundle_asset_source(
206        &self,
207        bundle_source: &BinarySource,
208        asset_index: usize,
209        path_id: i64,
210    ) -> Option<BinaryObjectRef<'_>> {
211        let typetree_options = self.options.typetree;
212        let reporter = self.reporter.clone();
213
214        let (key, bundle) = self.bundles.get_key_value(bundle_source)?;
215        let asset = bundle.assets.get(asset_index)?;
216        let object = asset.find_object_handle(path_id)?;
217        Some(BinaryObjectRef {
218            source: key,
219            source_kind: BinarySourceKind::AssetBundle,
220            asset_index: Some(asset_index),
221            object,
222            typetree_options,
223            reporter,
224        })
225    }
226
227    /// Find globally-unique keys for all matching objects by `path_id` (best-effort).
228    pub fn find_binary_object_keys(&self, path_id: i64) -> Vec<BinaryObjectKey> {
229        self.find_binary_objects(path_id)
230            .into_iter()
231            .map(|r| r.key())
232            .collect()
233    }
234
235    /// Find globally-unique keys for all matching objects by `path_id` within a specific source.
236    pub fn find_binary_object_keys_in_source<P: AsRef<Path>>(
237        &self,
238        source: P,
239        path_id: i64,
240    ) -> Vec<BinaryObjectKey> {
241        self.find_binary_objects_in_source(source, path_id)
242            .into_iter()
243            .map(|r| r.key())
244            .collect()
245    }
246
247    /// Read a `UnityObject` from a globally-unique key.
248    pub fn read_binary_object_key(&self, key: &BinaryObjectKey) -> Result<UnityObject> {
249        let typetree_options = self.options.typetree;
250        match key.source_kind {
251            BinarySourceKind::SerializedFile => {
252                let file = self.binary_assets.get(&key.source).ok_or_else(|| {
253                    UnityAssetError::format(format!(
254                        "SerializedFile source not loaded: {}",
255                        key.source.describe()
256                    ))
257                })?;
258                let object = file.find_object_handle(key.path_id).ok_or_else(|| {
259                    UnityAssetError::format(format!(
260                        "Object not found in SerializedFile {}: path_id={}",
261                        key.source.describe(),
262                        key.path_id
263                    ))
264                })?;
265                let obj = object.read_with_options(typetree_options).map_err(|e| {
266                    UnityAssetError::with_source("Failed to parse binary object", e)
267                })?;
268                if let Some(reporter) = &self.reporter {
269                    for w in obj.typetree_warnings() {
270                        reporter.typetree_warning(key, w);
271                    }
272                }
273                Ok(obj)
274            }
275            BinarySourceKind::AssetBundle => {
276                let bundle = self.bundles.get(&key.source).ok_or_else(|| {
277                    UnityAssetError::format(format!(
278                        "AssetBundle source not loaded: {}",
279                        key.source.describe()
280                    ))
281                })?;
282                let asset_index = key.asset_index.ok_or_else(|| {
283                    UnityAssetError::format(
284                        "AssetBundle key requires an asset_index (which asset in the bundle?)"
285                            .to_string(),
286                    )
287                })?;
288                let file = bundle.assets.get(asset_index).ok_or_else(|| {
289                    UnityAssetError::format(format!(
290                        "AssetBundle asset index out of range: {} asset_index={}",
291                        key.source.describe(),
292                        asset_index
293                    ))
294                })?;
295                let object = file.find_object_handle(key.path_id).ok_or_else(|| {
296                    UnityAssetError::format(format!(
297                        "Object not found in AssetBundle {} asset_index={}: path_id={}",
298                        key.source.describe(),
299                        asset_index,
300                        key.path_id
301                    ))
302                })?;
303                let obj = object.read_with_options(typetree_options).map_err(|e| {
304                    UnityAssetError::with_source("Failed to parse binary object", e)
305                })?;
306                if let Some(reporter) = &self.reporter {
307                    for w in obj.typetree_warnings() {
308                        reporter.typetree_warning(key, w);
309                    }
310                }
311                Ok(obj)
312            }
313        }
314    }
315
316    /// Best-effort peek of `m_Name`/`name` for a binary object key.
317    ///
318    /// This uses a TypeTree prefix fast path (when possible) and returns `Ok(None)` when the
319    /// object has no TypeTree or does not expose a name field.
320    pub fn peek_binary_object_name(&self, key: &BinaryObjectKey) -> Result<Option<String>> {
321        let typetree_options = self.options.typetree;
322        match key.source_kind {
323            BinarySourceKind::SerializedFile => {
324                let file = self.binary_assets.get(&key.source).ok_or_else(|| {
325                    UnityAssetError::format(format!(
326                        "SerializedFile source not loaded: {}",
327                        key.source.describe()
328                    ))
329                })?;
330                let object = file.find_object_handle(key.path_id).ok_or_else(|| {
331                    UnityAssetError::format(format!(
332                        "Object not found in SerializedFile {}: path_id={}",
333                        key.source.describe(),
334                        key.path_id
335                    ))
336                })?;
337                object
338                    .peek_name_with_options(typetree_options)
339                    .map_err(|e| {
340                        UnityAssetError::with_source("Failed to peek binary object name", e)
341                    })
342            }
343            BinarySourceKind::AssetBundle => {
344                let bundle = self.bundles.get(&key.source).ok_or_else(|| {
345                    UnityAssetError::format(format!(
346                        "AssetBundle source not loaded: {}",
347                        key.source.describe()
348                    ))
349                })?;
350                let asset_index = key.asset_index.ok_or_else(|| {
351                    UnityAssetError::format(
352                        "AssetBundle key requires an asset_index (which asset in the bundle?)"
353                            .to_string(),
354                    )
355                })?;
356                let file = bundle.assets.get(asset_index).ok_or_else(|| {
357                    UnityAssetError::format(format!(
358                        "AssetBundle asset index out of range: {} asset_index={}",
359                        key.source.describe(),
360                        asset_index
361                    ))
362                })?;
363                let object = file.find_object_handle(key.path_id).ok_or_else(|| {
364                    UnityAssetError::format(format!(
365                        "Object not found in AssetBundle {} asset_index={}: path_id={}",
366                        key.source.describe(),
367                        asset_index,
368                        key.path_id
369                    ))
370                })?;
371                object
372                    .peek_name_with_options(typetree_options)
373                    .map_err(|e| {
374                        UnityAssetError::with_source("Failed to peek binary object name", e)
375                    })
376            }
377        }
378    }
379}