unity_asset/environment/imp/
key.rs

1use std::path::PathBuf;
2use std::str::FromStr;
3
4use super::{BinaryObjectKey, BinarySource, BinarySourceKind};
5
6impl std::fmt::Display for BinaryObjectKey {
7    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8        let kind = match self.source_kind {
9            BinarySourceKind::SerializedFile => "serialized",
10            BinarySourceKind::AssetBundle => "bundle",
11        };
12        let asset_index = self
13            .asset_index
14            .map(|i| i.to_string())
15            .unwrap_or_else(|| "-".to_string());
16
17        let (outer, entry) = match &self.source {
18            BinarySource::Path(p) => (p.to_string_lossy().to_string(), String::new()),
19            BinarySource::WebEntry {
20                web_path,
21                entry_name,
22            } => (web_path.to_string_lossy().to_string(), entry_name.clone()),
23        };
24        write!(
25            f,
26            "bok2|{}|{}|{}|{}|{}|{}|{}",
27            kind,
28            asset_index,
29            self.path_id,
30            outer.len(),
31            outer,
32            entry.len(),
33            entry
34        )
35    }
36}
37
38impl FromStr for BinaryObjectKey {
39    type Err = String;
40
41    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
42        if s.starts_with("bok2|") {
43            return parse_bok2(s);
44        }
45        if s.starts_with("bok1|") {
46            return parse_bok1(s);
47        }
48        Err("invalid key prefix (expected: bok1|... or bok2|...)".to_string())
49    }
50}
51
52fn parse_kind(kind: &str) -> std::result::Result<BinarySourceKind, String> {
53    match kind {
54        "bundle" => Ok(BinarySourceKind::AssetBundle),
55        "serialized" => Ok(BinarySourceKind::SerializedFile),
56        other => Err(format!("unknown kind: {}", other)),
57    }
58}
59
60fn parse_asset_index(asset_index: &str) -> std::result::Result<Option<usize>, String> {
61    if asset_index == "-" || asset_index.is_empty() {
62        return Ok(None);
63    }
64    Ok(Some(
65        asset_index
66            .parse::<usize>()
67            .map_err(|e| format!("invalid asset_index: {}", e))?,
68    ))
69}
70
71fn parse_bok1(s: &str) -> std::result::Result<BinaryObjectKey, String> {
72    let prefix = "bok1|";
73    let mut rest = &s[prefix.len()..];
74    let (kind, r) = split_once(rest, '|').ok_or_else(|| "missing kind".to_string())?;
75    rest = r;
76    let (asset_index, r) =
77        split_once(rest, '|').ok_or_else(|| "missing asset_index".to_string())?;
78    rest = r;
79    let (path_id, r) = split_once(rest, '|').ok_or_else(|| "missing path_id".to_string())?;
80    rest = r;
81    let (path_len, path) =
82        split_once(rest, '|').ok_or_else(|| "missing path_len/path".to_string())?;
83
84    let source_kind = parse_kind(kind)?;
85    let asset_index = parse_asset_index(asset_index)?;
86    let path_id = path_id
87        .parse::<i64>()
88        .map_err(|e| format!("invalid path_id: {}", e))?;
89
90    let expected_len = path_len
91        .parse::<usize>()
92        .map_err(|e| format!("invalid path_len: {}", e))?;
93    if path.len() != expected_len {
94        return Err(format!(
95            "path length mismatch: expected {} bytes, got {} bytes",
96            expected_len,
97            path.len()
98        ));
99    }
100
101    if source_kind == BinarySourceKind::AssetBundle && asset_index.is_none() {
102        return Err("asset_index is required for bundle keys".to_string());
103    }
104
105    Ok(BinaryObjectKey {
106        source: BinarySource::Path(PathBuf::from(path)),
107        source_kind,
108        asset_index,
109        path_id,
110    })
111}
112
113fn parse_bok2(s: &str) -> std::result::Result<BinaryObjectKey, String> {
114    let prefix = "bok2|";
115    let mut rest = &s[prefix.len()..];
116
117    let (kind, r) = split_once(rest, '|').ok_or_else(|| "missing kind".to_string())?;
118    rest = r;
119    let (asset_index, r) =
120        split_once(rest, '|').ok_or_else(|| "missing asset_index".to_string())?;
121    rest = r;
122    let (path_id, r) = split_once(rest, '|').ok_or_else(|| "missing path_id".to_string())?;
123    rest = r;
124    let (outer_len, r) = split_once(rest, '|').ok_or_else(|| "missing outer_len".to_string())?;
125    rest = r;
126
127    let source_kind = parse_kind(kind)?;
128    let asset_index = parse_asset_index(asset_index)?;
129    let path_id = path_id
130        .parse::<i64>()
131        .map_err(|e| format!("invalid path_id: {}", e))?;
132
133    let outer_len = outer_len
134        .parse::<usize>()
135        .map_err(|e| format!("invalid outer_len: {}", e))?;
136    if rest.len() < outer_len {
137        return Err("outer is shorter than outer_len".to_string());
138    }
139
140    let outer = rest
141        .get(..outer_len)
142        .ok_or_else(|| "outer_len splits UTF-8 boundary".to_string())?;
143    let rest = rest
144        .get(outer_len..)
145        .ok_or_else(|| "outer_len splits UTF-8 boundary".to_string())?;
146
147    let rest = rest
148        .strip_prefix('|')
149        .ok_or_else(|| "missing entry delimiter".to_string())?;
150    let (entry_len, rest) = split_once(rest, '|').ok_or_else(|| "missing entry_len".to_string())?;
151    let entry_len = entry_len
152        .parse::<usize>()
153        .map_err(|e| format!("invalid entry_len: {}", e))?;
154    if rest.len() != entry_len {
155        return Err(format!(
156            "entry length mismatch: expected {} bytes, got {} bytes",
157            entry_len,
158            rest.len()
159        ));
160    }
161
162    if source_kind == BinarySourceKind::AssetBundle && asset_index.is_none() {
163        return Err("asset_index is required for bundle keys".to_string());
164    }
165
166    let source = if entry_len == 0 {
167        BinarySource::Path(PathBuf::from(outer))
168    } else {
169        BinarySource::WebEntry {
170            web_path: PathBuf::from(outer),
171            entry_name: rest.to_string(),
172        }
173    };
174
175    Ok(BinaryObjectKey {
176        source,
177        source_kind,
178        asset_index,
179        path_id,
180    })
181}
182
183fn split_once(s: &str, delim: char) -> Option<(&str, &str)> {
184    let pos = s.find(delim)?;
185    Some((&s[..pos], &s[pos + delim.len_utf8()..]))
186}