1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use std::{collections::HashMap, io::Read};

use flate2::read::GzDecoder;
use regex::Regex;
use tar::Archive;
use thiserror::Error;
use uuid::Uuid;

use crate::{Package, PackageAssetBuilder};

#[derive(Error, Debug)]
pub enum PackageReadError {
    #[error("Failed to read package: {0}")]
    ReadError(#[from] std::io::Error),
    #[error("Failed to decode gzip stream: {0}")]
    GzipError(#[from] flate2::DecompressError),
    #[error("Failed to parse UUID: {0}")]
    UuidError(#[from] uuid::Error),
    #[error("Invalid asset path: {0}")]
    InvalidAssetPath(String),
}

pub fn read_package<R: Read>(r: R) -> Result<Package, PackageReadError> {
    let mut package = Package::new();
    let decoder = GzDecoder::new(r);
    let mut archive = Archive::new(decoder);

    let entries = archive.entries()?;

    let regex = Regex::new(r"\.\/([0-9a-f]{32})\/(.*)").unwrap();

    let mut found_assets: HashMap<Uuid, PackageAssetBuilder> = HashMap::new();
    for entry in entries {
        let mut entry = entry?;
        let path = entry.path()?;
        let path_str = path
            .to_str()
            .ok_or_else(|| PackageReadError::InvalidAssetPath(format!("{:?}", path)))?;

        if let Some(captures) = regex.captures(path_str) {
            let asset_guid = Uuid::parse_str(&captures[1])?;
            let asset_path = &captures[2];

            let builder = found_assets
                .entry(asset_guid)
                .or_insert_with(PackageAssetBuilder::default);

            match asset_path {
                "pathname" => {
                    let mut pathname = String::new();
                    entry
                        .read_to_string(&mut pathname)
                        .expect("Cannot read pathname of asset");
                    builder.pathname = Some(pathname);
                }
                "preview.png" => {
                    let mut preview = Vec::new();
                    entry
                        .read_to_end(&mut preview)
                        .expect("Cannot read preview of asset");
                    builder.preview = Some(preview);
                }
                "asset.meta" => {
                    let mut meta = Vec::new();
                    entry
                        .read_to_end(&mut meta)
                        .expect("Cannot read meta of asset");
                    builder.meta = Some(meta);
                }
                "asset" => {
                    let mut data = Vec::new();
                    entry
                        .read_to_end(&mut data)
                        .expect("Cannot read data of asset");
                    builder.data = Some(data);
                }
                _ => {}
            }
        }
    }

    for (uuid, builder) in found_assets {
        package.assets.insert(uuid, builder.build());
    }

    Ok(package)
}

#[cfg(test)]
mod tests {
    use std::{fs::File, path::PathBuf};

    use uuid::uuid;

    use super::*;

    fn get_package_file(path: &str) -> File {
        let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        root.push(format!("tests/{}.unitypackage", path));
        File::open(root).unwrap()
    }

    #[test]
    fn asset_simple_material() {
        let package = read_package(get_package_file("simple-material")).unwrap();

        assert_eq!(package.assets.len(), 1);
        assert!(package
            .assets
            .contains_key(&uuid!("c24c6def6556015fb913fec2280e3315")));
    }

    #[test]
    fn asset_simple_cube() {
        let package = read_package(get_package_file("simple-cube")).unwrap();

        assert_eq!(package.assets.len(), 2);
        assert!(package
            .assets
            .contains_key(&uuid!("c24c6def6556015fb913fec2280e3315")));
        assert!(package
            .assets
            .contains_key(&uuid!("8109a09196ba303c59774d4f4048f48c")));
    }

    #[test]
    fn asset_invalid_uuid() {
        let package = read_package(get_package_file("invalid-uuid")).unwrap();

        assert_eq!(package.assets.len(), 0);
    }

    #[test]
    fn empty_archive() {
        let package = read_package(get_package_file("empty")).unwrap();

        assert_eq!(package.assets.len(), 0);
    }
}