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::value::Value;
12use crate::{Error, Result, invalid};
13
14#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct StreamingInfo {
17 pub offset: u64,
19 pub size: u64,
21 pub path: String,
23}
24
25impl StreamingInfo {
26 pub fn of(v: &Value) -> Result<Option<StreamingInfo>> {
28 let num = |k: &str| {
29 v.get(k)
30 .and_then(|x| match x {
31 Value::UInt(u) => Some(*u),
32 Value::Int(i) => u64::try_from(*i).ok(),
33 _ => None,
34 })
35 .ok_or_else(|| invalid(format!("StreamingInfo: no {k}")))
36 };
37 let path = v
38 .get("path")
39 .and_then(Value::as_str)
40 .ok_or_else(|| invalid("StreamingInfo: no path"))?;
41 let (offset, size) = (num("offset")?, num("size")?);
42 if path.is_empty() || size == 0 {
43 return Ok(None);
44 }
45 Ok(Some(StreamingInfo {
46 offset,
47 size,
48 path: path.to_owned(),
49 }))
50 }
51
52 pub fn file_in(&self, dir: &Path) -> Result<PathBuf> {
55 let name = self.path.rsplit('/').next().unwrap_or(&self.path);
56 if name.is_empty() || name == "." || name == ".." || name.contains('\\') {
57 return Err(invalid(format!("streamed resource path {:?}", self.path)));
58 }
59 Ok(dir.join(name))
60 }
61
62 pub fn read(&self, dir: &Path) -> Result<Vec<u8>> {
64 use std::io::{Read, Seek, SeekFrom};
65 let path = self.file_in(dir)?;
66 let io = |source| Error::Io {
67 path: path.clone(),
68 source,
69 };
70 let mut f = std::fs::File::open(&path).map_err(io)?;
71 let len = f.metadata().map_err(io)?.len();
72 if self.offset.checked_add(self.size).is_none_or(|end| end > len) {
73 return Err(invalid(format!(
74 "{}: {}+{} past its end ({len} bytes)",
75 path.display(),
76 self.offset,
77 self.size
78 )));
79 }
80 f.seek(SeekFrom::Start(self.offset)).map_err(io)?;
81 let size = usize::try_from(self.size).map_err(|_| {
82 invalid(format!(
83 "{}: {} bytes do not fit memory",
84 path.display(),
85 self.size
86 ))
87 })?;
88 let mut out = vec![0; size];
89 f.read_exact(&mut out).map_err(io)?;
90 Ok(out)
91 }
92}
93
94pub fn streaming_infos(v: &Value) -> Vec<(String, &Value)> {
96 fn walk<'a>(v: &'a Value, path: &str, out: &mut Vec<(String, &'a Value)>) {
97 match v {
98 Value::Struct(fields) => {
99 if v.get("offset").is_some() && v.get("size").is_some() && v.get("path").is_some() {
100 out.push((path.to_owned(), v));
101 return;
102 }
103 for (k, x) in fields {
104 walk(x, &format!("{path}.{k}"), out);
105 }
106 }
107 Value::Array(crate::value::Array::Values(items)) => {
108 for (i, x) in items.iter().enumerate() {
109 walk(x, &format!("{path}[{i}]"), out);
110 }
111 }
112 Value::Pair(p) => {
113 walk(&p.0, path, out);
114 walk(&p.1, path, out);
115 }
116 _ => {}
117 }
118 }
119 let mut out = Vec::new();
120 walk(v, "$", &mut out);
121 out
122}