unity_asset/environment/imp/
pptr.rs

1use super::*;
2
3impl Environment {
4    fn find_loaded_serialized_source_by_external_path(
5        &self,
6        external_path: &str,
7    ) -> Option<BinarySource> {
8        if external_path.is_empty() {
9            return None;
10        }
11
12        let direct = Path::new(external_path);
13        let direct_key = BinarySource::Path(direct.to_path_buf());
14        if self.binary_assets.contains_key(&direct_key) {
15            return Some(direct_key);
16        }
17
18        if !direct.is_absolute() {
19            let joined = self.base_path.join(direct);
20            let joined_key = BinarySource::Path(joined);
21            if self.binary_assets.contains_key(&joined_key) {
22                return Some(joined_key);
23            }
24        }
25
26        let target_file_name = direct.file_name().and_then(|n| n.to_str());
27        let mut by_name: Vec<&PathBuf> = Vec::new();
28        if let Some(name) = target_file_name {
29            by_name.extend(
30                self.binary_assets
31                    .keys()
32                    .filter_map(|k| k.as_path())
33                    .filter(|p| p.file_name().and_then(|n| n.to_str()) == Some(name)),
34            );
35        }
36        by_name.sort();
37        if let Some(found) = by_name.first() {
38            return Some(BinarySource::Path((*found).clone()));
39        }
40
41        let external_norm = external_path.replace('\\', "/");
42        let mut by_suffix: Vec<&PathBuf> = self
43            .binary_assets
44            .keys()
45            .filter_map(|k| k.as_path())
46            .filter(|p| {
47                let p_str = p.to_string_lossy().replace('\\', "/");
48                p_str.ends_with(&external_norm) || external_norm.ends_with(&p_str)
49            })
50            .collect();
51        by_suffix.sort();
52        by_suffix.first().cloned().cloned().map(BinarySource::Path)
53    }
54
55    /// Resolve a Unity `PPtr` (`fileID`, `pathID`) into a globally-unique object key.
56    ///
57    /// Notes:
58    /// - `file_id == 0` points to the same `SerializedFile` as the context object.
59    /// - `file_id > 0` indexes into the context file's `externals` list (Unity convention: `file_id - 1`).
60    /// - External resolution is best-effort and currently only matches already-loaded standalone serialized files.
61    pub fn resolve_binary_pptr(
62        &self,
63        context: &BinaryObjectRef<'_>,
64        file_id: i32,
65        path_id: i64,
66    ) -> Option<BinaryObjectKey> {
67        if file_id == 0 {
68            return Some(BinaryObjectKey {
69                source: context.source.clone(),
70                source_kind: context.source_kind,
71                asset_index: context.asset_index,
72                path_id,
73            });
74        }
75
76        if file_id < 0 {
77            return None;
78        }
79
80        let idx: usize = (file_id - 1).try_into().ok()?;
81        let external = context.object.file().externals.get(idx)?;
82
83        // Best-effort: if the context object comes from a bundle, resolve external references to other
84        // serialized files inside the same bundle.
85        if context.source_kind == BinarySourceKind::AssetBundle
86            && let Some(bundle) = self.bundles.get(context.source)
87        {
88            let external_norm = external.path.replace('\\', "/");
89            let external_file_name = std::path::Path::new(&external_norm)
90                .file_name()
91                .and_then(|n| n.to_str());
92
93            let mut candidates: Vec<(usize, &String)> =
94                bundle.asset_names.iter().enumerate().collect();
95            candidates.sort_by(|a, b| a.1.cmp(b.1));
96
97            if let Some((asset_index, _)) = candidates.into_iter().find(|(_, name)| {
98                let name_norm = name.replace('\\', "/");
99                if name_norm == external_norm {
100                    return true;
101                }
102                if name_norm.ends_with(&external_norm) || external_norm.ends_with(&name_norm) {
103                    return true;
104                }
105                match external_file_name {
106                    Some(file_name) => {
107                        std::path::Path::new(&name_norm)
108                            .file_name()
109                            .and_then(|n| n.to_str())
110                            == Some(file_name)
111                    }
112                    None => false,
113                }
114            }) {
115                return Some(BinaryObjectKey {
116                    source: context.source.clone(),
117                    source_kind: BinarySourceKind::AssetBundle,
118                    asset_index: Some(asset_index),
119                    path_id,
120                });
121            }
122        }
123
124        // Fallback: resolve to an already-loaded standalone serialized file on disk.
125        let resolved_source =
126            self.find_loaded_serialized_source_by_external_path(&external.path)?;
127        Some(BinaryObjectKey {
128            source: resolved_source,
129            source_kind: BinarySourceKind::SerializedFile,
130            asset_index: None,
131            path_id,
132        })
133    }
134
135    /// Resolve and parse a Unity `PPtr` (`fileID`, `pathID`) using a context object for external mapping.
136    pub fn read_binary_pptr(
137        &self,
138        context: &BinaryObjectRef<'_>,
139        file_id: i32,
140        path_id: i64,
141    ) -> Result<UnityObject> {
142        let key = self
143            .resolve_binary_pptr(context, file_id, path_id)
144            .ok_or_else(|| {
145                UnityAssetError::format(format!(
146                    "Failed to resolve PPtr: file_id={}, path_id={}",
147                    file_id, path_id
148                ))
149            })?;
150        self.read_binary_object_key(&key)
151    }
152}