unity_formats/
resource.rs1#![allow(
4 clippy::disallowed_methods,
5 clippy::disallowed_types,
6 reason = "reader I/O: reads streamed resource files"
7)]
8
9use std::path::{Path, PathBuf};
10
11use crate::bundle::{Bundle, BundleNode};
12use crate::value::Value;
13use crate::{Error, Result, invalid};
14
15#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct StreamingInfo {
18 pub offset: u64,
20 pub size: u64,
22 pub path: String,
24}
25
26impl StreamingInfo {
27 pub fn of(v: &Value) -> Result<Option<StreamingInfo>> {
29 let num = |k: &str| {
30 v.get(k)
31 .and_then(|x| match x {
32 Value::UInt(u) => Some(*u),
33 Value::Int(i) => u64::try_from(*i).ok(),
34 _ => None,
35 })
36 .ok_or_else(|| invalid(format!("StreamingInfo: no {k}")))
37 };
38 let path = v
39 .get("path")
40 .and_then(Value::as_str)
41 .ok_or_else(|| invalid("StreamingInfo: no path"))?;
42 let (offset, size) = (num("offset")?, num("size")?);
43 if path.is_empty() || size == 0 {
44 return Ok(None);
45 }
46 Ok(Some(StreamingInfo {
47 offset,
48 size,
49 path: path.to_owned(),
50 }))
51 }
52
53 pub fn file_in(&self, dir: &Path) -> Result<PathBuf> {
56 let name = self.path.rsplit('/').next().unwrap_or(&self.path);
57 if name.is_empty() || name == "." || name == ".." || name.contains('\\') {
58 return Err(invalid(format!("streamed resource path {:?}", self.path)));
59 }
60 Ok(dir.join(name))
61 }
62
63 pub fn read(&self, dir: &Path) -> Result<Vec<u8>> {
65 use std::io::{Read, Seek, SeekFrom};
66 let path = self.file_in(dir)?;
67 let io = |source| Error::Io {
68 path: path.clone(),
69 source,
70 };
71 let mut f = std::fs::File::open(&path).map_err(io)?;
72 let len = f.metadata().map_err(io)?.len();
73 if self.offset.checked_add(self.size).is_none_or(|end| end > len) {
74 return Err(invalid(format!(
75 "{}: {}+{} past its end ({len} bytes)",
76 path.display(),
77 self.offset,
78 self.size
79 )));
80 }
81 f.seek(SeekFrom::Start(self.offset)).map_err(io)?;
82 let size = usize::try_from(self.size).map_err(|_| {
83 invalid(format!(
84 "{}: {} bytes do not fit memory",
85 path.display(),
86 self.size
87 ))
88 })?;
89 let mut out = vec![0; size];
90 f.read_exact(&mut out).map_err(io)?;
91 Ok(out)
92 }
93}
94
95impl StreamingInfo {
96 pub fn node_in<'b>(&self, bundle: &'b Bundle) -> Result<&'b BundleNode> {
100 let last = |p: &str| {
101 p.replace('\\', "/")
102 .rsplit('/')
103 .next()
104 .unwrap_or(p)
105 .to_lowercase()
106 };
107 let name = last(&self.path);
108 let mut found = bundle
109 .nodes()
110 .iter()
111 .filter(|n| !n.is_serialized() && last(&n.path) == name);
112 let (Some(node), None) = (found.next(), found.next()) else {
113 return Err(invalid(format!(
114 "{}: streamed resource {:?} is not one stream node of the bundle",
115 bundle.path().display(),
116 self.path
117 )));
118 };
119 if self
120 .offset
121 .checked_add(self.size)
122 .is_none_or(|end| end > node.size)
123 {
124 return Err(invalid(format!(
125 "{}: {}: {}+{} past its end ({} bytes)",
126 bundle.path().display(),
127 node.path,
128 self.offset,
129 self.size,
130 node.size
131 )));
132 }
133 Ok(node)
134 }
135
136 pub fn read_bundled(&self, bundle: &Bundle) -> Result<Vec<u8>> {
138 let node = self.node_in(bundle)?;
139 let len = usize::try_from(self.size)
140 .map_err(|_| invalid(format!("{}: {} bytes do not fit memory", node.path, self.size)))?;
141 bundle.read(node, self.offset, len)
142 }
143}
144
145pub fn streaming_infos(v: &Value) -> Vec<(String, &Value)> {
147 fn walk<'a>(v: &'a Value, path: &str, out: &mut Vec<(String, &'a Value)>) {
148 match v {
149 Value::Struct(fields) => {
150 if v.get("offset").is_some() && v.get("size").is_some() && v.get("path").is_some() {
151 out.push((path.to_owned(), v));
152 return;
153 }
154 for (k, x) in fields {
155 walk(x, &format!("{path}.{k}"), out);
156 }
157 }
158 Value::Array(crate::value::Array::Values(items)) => {
159 for (i, x) in items.iter().enumerate() {
160 walk(x, &format!("{path}[{i}]"), out);
161 }
162 }
163 Value::Pair(p) => {
164 walk(&p.0, path, out);
165 walk(&p.1, path, out);
166 }
167 _ => {}
168 }
169 }
170 let mut out = Vec::new();
171 walk(v, "$", &mut out);
172 out
173}