unitypkg_core/
reader.rs

1use std::{collections::HashMap, io::Read};
2
3use flate2::read::GzDecoder;
4use regex::Regex;
5use tar::Archive;
6use thiserror::Error;
7use uuid::Uuid;
8
9use crate::{Package, PackageAssetBuilder};
10
11#[derive(Error, Debug)]
12pub enum PackageReadError {
13    #[error("Failed to read package: {0}")]
14    ReadError(#[from] std::io::Error),
15    #[error("Failed to decode gzip stream: {0}")]
16    GzipError(#[from] flate2::DecompressError),
17    #[error("Failed to parse UUID: {0}")]
18    UuidError(#[from] uuid::Error),
19    #[error("Invalid asset path: {0}")]
20    InvalidAssetPath(String),
21}
22
23pub fn read_package<R: Read>(r: R) -> Result<Package, PackageReadError> {
24    let mut package = Package::new();
25    let decoder = GzDecoder::new(r);
26    let mut archive = Archive::new(decoder);
27
28    let entries = archive.entries()?;
29
30    let regex = Regex::new(r"\.\/([0-9a-f]{32})\/(.*)").unwrap();
31
32    let mut found_assets: HashMap<Uuid, PackageAssetBuilder> = HashMap::new();
33    for entry in entries {
34        let mut entry = entry?;
35        let path = entry.path()?;
36        let path_str = path
37            .to_str()
38            .ok_or_else(|| PackageReadError::InvalidAssetPath(format!("{:?}", path)))?;
39
40        if let Some(captures) = regex.captures(path_str) {
41            let asset_guid = Uuid::parse_str(&captures[1])?;
42            let asset_path = &captures[2];
43
44            let builder = found_assets.entry(asset_guid).or_default();
45
46            match asset_path {
47                "pathname" => {
48                    let mut pathname = String::new();
49                    entry.read_to_string(&mut pathname)?;
50                    builder.pathname = Some(pathname);
51                }
52                "preview.png" => {
53                    let mut preview = Vec::new();
54                    entry.read_to_end(&mut preview)?;
55                    builder.preview = Some(preview);
56                }
57                "asset.meta" => {
58                    let mut meta = Vec::new();
59                    entry.read_to_end(&mut meta)?;
60                    builder.meta = Some(meta);
61                }
62                "asset" => {
63                    let mut data = Vec::new();
64                    entry.read_to_end(&mut data)?;
65                    builder.data = Some(data);
66                }
67                _ => {}
68            }
69        }
70    }
71
72    for (uuid, builder) in found_assets {
73        package.assets.insert(uuid, builder.build());
74    }
75
76    Ok(package)
77}
78
79#[cfg(test)]
80mod tests {
81    use std::{fs::File, path::PathBuf};
82
83    use uuid::uuid;
84
85    use super::*;
86
87    fn get_package_file(path: &str) -> File {
88        let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
89        root.push(format!("tests/{}.unitypackage", path));
90        File::open(root).unwrap()
91    }
92
93    #[test]
94    fn asset_simple_material() {
95        let package = read_package(get_package_file("simple-material")).unwrap();
96
97        assert_eq!(package.assets.len(), 1);
98        assert!(package
99            .assets
100            .contains_key(&uuid!("c24c6def6556015fb913fec2280e3315")));
101    }
102
103    #[test]
104    fn asset_simple_cube() {
105        let package = read_package(get_package_file("simple-cube")).unwrap();
106
107        assert_eq!(package.assets.len(), 2);
108        assert!(package
109            .assets
110            .contains_key(&uuid!("c24c6def6556015fb913fec2280e3315")));
111        assert!(package
112            .assets
113            .contains_key(&uuid!("8109a09196ba303c59774d4f4048f48c")));
114    }
115
116    #[test]
117    fn asset_invalid_uuid() {
118        let package = read_package(get_package_file("invalid-uuid")).unwrap();
119
120        assert_eq!(package.assets.len(), 0);
121    }
122
123    #[test]
124    fn empty_archive() {
125        let package = read_package(get_package_file("empty")).unwrap();
126
127        assert_eq!(package.assets.len(), 0);
128    }
129}