unity_asset/environment/imp/
key.rs1use 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}