├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── .github └── workflows │ └── rust.yml ├── src ├── main.rs ├── lib.rs ├── package.rs └── commands.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .vscode/ 3 | /FBX2glTF 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unitypackage_util" 3 | version = "0.1.3" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | base64 = "0.21.0" 10 | clap = { version = "4.2.5", features = ["derive"] } 11 | exitcode = "1.1.2" 12 | flate2 = "1.0.25" 13 | infer = "0.13.0" 14 | regex = "1.8.1" 15 | serde = { version = "1.0.160", features = ["derive"] } 16 | serde_json = "1.0.96" 17 | serde_yaml = "0.9.21" 18 | tar = "0.4.38" 19 | unity-yaml-rust = "0.1.1" 20 | walkdir = "2.3.3" 21 | xxhash-rust = { version = "0.8.6", features = ["xxh64"] } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 barcoderdev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unitypackage_util 2 | 3 | Requires `barcoderdev/FBX2glTF` to extract FBX in GLTF(glb) format. Place the binary in the same folder, or in PATH. 4 | 5 | --- 6 | 7 | ```bash 8 | Usage: unitypackage_util 9 | 10 | Commands: 11 | debug Find source of dump crashes 12 | info Show package info 13 | name Display path from guid/pathname file 14 | dump Dump package contents 15 | list List package contents 16 | extract Extract package file 17 | xx-hash Calculate xxhash 64 of string 18 | help Print this message or the help of the given subcommand(s) 19 | 20 | Arguments: 21 | Unity Package (Tar, TarGz, or Folder) 22 | 23 | Options: 24 | -h, --help Print help 25 | -V, --version Print version 26 | ``` 27 | 28 | --- 29 | 30 | ```bash 31 | Extract package file 32 | 33 | Usage: unitypackage_util extract [OPTIONS] 34 | 35 | Arguments: 36 | 37 | 38 | Options: 39 | -o, --output-file Extract to file 40 | -m, --meta Extract /asset.meta file instead of /asset 41 | -j, --json Process yaml to json 42 | -p, --pretty Pretty Print JSON 43 | -f, --fbx2gltf Convert FBX to GLTF 44 | -b, --base64 Base64 encode output 45 | -h, --help Print help 46 | ``` 47 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | workflow_dispatch: 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | build-macos: 15 | runs-on: macos-11 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | 20 | - name: Cargo build 21 | run: cargo build --verbose --release 22 | shell: bash 23 | 24 | - name: Run unitypackage_util help 25 | run: ./target/release/unitypackage_util --help 26 | shell: bash 27 | 28 | - name: Adhoc signature 29 | run: | 30 | codesign -s - --options=runtime target/release/unitypackage_util 31 | shell: bash 32 | 33 | - name: Prepare artifacts 34 | run: | 35 | export TARGET=unitypackage_util-macos-x86_64 36 | mkdir $TARGET 37 | cp LICENSE $TARGET/unitypackage_util-License.txt 38 | cp target/release/unitypackage_util $TARGET/unitypackage_util-macos-x86_64 39 | 7z a -r $TARGET.zip $TARGET 40 | shell: bash 41 | 42 | - name: Release 43 | uses: softprops/action-gh-release@v1 44 | if: startsWith(github.ref, 'refs/tags/') 45 | with: 46 | files: unitypackage_util-macos-x86_64.zip 47 | 48 | - name: unitypackage_util-macos-x86_64 49 | uses: actions/upload-artifact@v3 50 | with: 51 | name: unitypackage_util-macos-x86_64 52 | path: unitypackage_util-macos-x86_64/* 53 | 54 | build-windows: 55 | runs-on: windows-2019 56 | steps: 57 | - name: Checkout 58 | uses: actions/checkout@v3 59 | 60 | - name: Cargo build 61 | run: cargo build --verbose --release 62 | shell: bash 63 | 64 | - name: Run unitypackage_util help 65 | run: | 66 | ./target/release/unitypackage_util.exe --help 67 | shell: bash 68 | 69 | - name: Prepare artifacts 70 | run: | 71 | export TARGET=unitypackage_util-windows-x86_64 72 | mkdir $TARGET 73 | cp LICENSE $TARGET/unitypackage_util-License.txt 74 | cp target/release/unitypackage_util.exe $TARGET/unitypackage_util-windows-x86_64.exe 75 | 7z a -r $TARGET.zip $TARGET 76 | shell: bash 77 | 78 | - name: Release 79 | uses: softprops/action-gh-release@v1 80 | if: startsWith(github.ref, 'refs/tags/') 81 | with: 82 | files: | 83 | unitypackage_util-windows-x86_64.zip 84 | unitypackage_util-windows-x86_64/unitypackage_util-License.txt 85 | 86 | - name: unitypackage_util-windows-x86_64 87 | uses: actions/upload-artifact@v3 88 | with: 89 | name: unitypackage_util-windows-x86_64 90 | path: unitypackage_util-windows-x86_64/* 91 | 92 | build-linux: 93 | runs-on: ubuntu-20.04 94 | steps: 95 | - name: Checkout 96 | uses: actions/checkout@v3 97 | 98 | - name: Cargo build 99 | run: cargo build --verbose --release 100 | shell: bash 101 | 102 | - name: Run unitypackage_util help 103 | run: ./target/release/unitypackage_util --help 104 | shell: bash 105 | 106 | - name: Prepare artifacts 107 | run: | 108 | export TARGET=unitypackage_util-linux-x86_64 109 | mkdir $TARGET 110 | cp LICENSE $TARGET/unitypackage_util-License.txt 111 | cp target/release/unitypackage_util $TARGET/unitypackage_util-linux-x86_64 112 | 7z a -r $TARGET.zip $TARGET 113 | shell: bash 114 | 115 | - name: Release 116 | uses: softprops/action-gh-release@v1 117 | if: startsWith(github.ref, 'refs/tags/') 118 | with: 119 | files: unitypackage_util-linux-x86_64.zip 120 | 121 | - name: unitypackage_util-linux-x86_64 122 | uses: actions/upload-artifact@v3 123 | with: 124 | name: unitypackage_util-linux-x86_64 125 | path: unitypackage_util-linux-x86_64/* 126 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | 3 | mod commands; 4 | mod package; 5 | 6 | //---------------------------------------- 7 | 8 | use clap::{Parser, Subcommand}; 9 | use std::path::PathBuf; 10 | 11 | //---------------------------------------- 12 | 13 | // https://rust-lang-nursery.github.io/rust-cookbook/file/dir.html 14 | // https://rust-lang-nursery.github.io/rust-cookbook/compression/tar.html 15 | // https://serde.rs 16 | 17 | //---------------------------------------- 18 | 19 | #[derive(Parser)] 20 | #[command( 21 | author, 22 | version, 23 | about, 24 | long_about = None, 25 | arg_required_else_help = true, 26 | subcommand_required = true 27 | )] 28 | struct Cli { 29 | /// Unity Package (Tar, TarGz, or Folder) 30 | package: PathBuf, 31 | 32 | #[command(subcommand)] 33 | command: Option, 34 | } 35 | 36 | //---------------------------------------- 37 | 38 | #[derive(Subcommand)] 39 | enum Commands { 40 | // Test, 41 | /// Find source of dump crashes 42 | Debug, 43 | /// Show package info 44 | Info, 45 | /// Display path from guid/pathname file 46 | Name { 47 | guid: String, 48 | }, 49 | /// Dump package contents 50 | Dump { 51 | /// Pretty Print JSON 52 | #[arg(short, long)] 53 | pretty: bool, 54 | }, 55 | /// List package contents 56 | List { 57 | /// Hide GUIDs 58 | #[arg(short, long)] 59 | no_guid: bool, 60 | 61 | /// Pretty Print JSON 62 | #[arg(short, long)] 63 | pretty: bool, 64 | 65 | /// Directory Filter 66 | #[arg(short, long)] 67 | dir: Option, 68 | }, 69 | /// Extract package file 70 | Extract { 71 | #[arg(required = true)] 72 | guid: Option, 73 | 74 | /// Extract to file 75 | #[arg(short, long)] 76 | output_file: Option, 77 | 78 | /// Extract /asset.meta file instead of /asset 79 | #[arg(short, long)] 80 | meta: bool, 81 | 82 | /// Process yaml to json 83 | #[arg(short, long)] 84 | json: bool, 85 | 86 | /// Pretty Print JSON 87 | #[arg(short, long)] 88 | pretty: bool, 89 | 90 | /// Convert FBX to GLTF 91 | #[arg(short, long)] 92 | fbx2gltf: bool, 93 | 94 | /// Base64 encode output 95 | #[arg(short, long)] 96 | base64: bool, 97 | }, 98 | /// Calculate xxhash 64 of string 99 | XxHash { 100 | #[arg(required = true)] 101 | text: String, 102 | } 103 | } 104 | 105 | //---------------------------------------- 106 | 107 | fn main() { 108 | let cli = Cli::parse(); 109 | 110 | let package_path = cli.package.to_str().unwrap(); 111 | 112 | match &cli.command { 113 | &Some(Commands::Info) => { 114 | println!( 115 | "Package: {}", 116 | package::Package::new(package_path).unwrap() 117 | ); 118 | } 119 | &Some(Commands::Dump { pretty }) => { 120 | commands::package_contents_dump(package_path, pretty, false); 121 | } 122 | &Some(Commands::Name { ref guid }) => { 123 | commands::package_contents_name(package_path, guid); 124 | } 125 | Some(Commands::List { 126 | no_guid, 127 | pretty, 128 | dir, 129 | }) => { 130 | commands::package_contents_list(package_path, dir, !*no_guid, *pretty); 131 | } 132 | &Some(Commands::Extract { 133 | ref guid, 134 | ref output_file, 135 | meta, 136 | json, 137 | pretty, 138 | fbx2gltf, 139 | base64, 140 | }) => { 141 | commands::package_file_extract( 142 | package_path, 143 | guid.as_ref().unwrap(), 144 | output_file, 145 | meta, 146 | json, 147 | pretty, 148 | fbx2gltf, 149 | base64, 150 | ); 151 | } 152 | // &Some(Commands::Test) => { 153 | // let package = package::Package::new(package_path); 154 | // // println!("{:?}", package); 155 | 156 | // if let Ok(package) = package { 157 | // let result = package.open(); 158 | // for file in result.unwrap().entries() { 159 | // let size = file.as_ref().unwrap().size().unwrap(); 160 | // let path = file.as_ref().unwrap().path().unwrap(); 161 | // println!("{} {}", size, path.display()); 162 | // } 163 | // } 164 | // } 165 | &Some(Commands::Debug) => { 166 | commands::package_contents_dump(package_path, false, true); 167 | } 168 | &Some(Commands::XxHash { ref text }) => { 169 | commands::xx_hash(text); 170 | } 171 | &None => (), 172 | } 173 | } 174 | 175 | //---------------------------------------- 176 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | 3 | use std::fs::File; 4 | 5 | use flate2::read::GzDecoder; 6 | use tar::Archive; 7 | 8 | use std::{ 9 | fs, 10 | io::{self}, 11 | ops::Add, 12 | }; 13 | 14 | use regex::Regex; 15 | 16 | //---------------------------------------- 17 | 18 | const UNITY_MACRO: &str = "--- !u!"; 19 | 20 | //---------------------------------------- 21 | 22 | pub fn asset_yaml_cleanup(yaml: &str) -> String { 23 | let mut class_id: i32 = 0; 24 | let mut file_id: i64 = 0; 25 | let mut extra: Option = None; 26 | 27 | let re = Regex::new(r"fileID: ([\-0-9]+)").unwrap(); 28 | let m_name_re = Regex::new(r"m_Name:").unwrap(); 29 | 30 | // Hacky fix for some MonoBehaviors having multiple m_Name's 31 | // See https://github.com/barcoderdev/unitypackage_godot/issues/14 32 | let mut m_name_count: u8 = 0; 33 | 34 | yaml.lines() 35 | .into_iter() 36 | .map(|line| { 37 | if line.starts_with(UNITY_MACRO) { 38 | m_name_count = 0; 39 | // Split "--- !u!CLASS_ID &FILE_ID EXTRA" into parts 40 | let mut chunks = line[UNITY_MACRO.len()..] 41 | .trim() 42 | .split_whitespace() 43 | .into_iter(); 44 | class_id = chunks.next().unwrap_or("").parse().unwrap(); 45 | file_id = chunks 46 | .next() 47 | .unwrap_or("") 48 | .replace("&", "") 49 | .parse() 50 | .unwrap_or(0); 51 | extra = chunks.next().map(|s| s.to_string()); 52 | // Keep the "---" document separator 53 | "---\n".to_string() 54 | } else if line.starts_with("%") || line.len() == 0 { 55 | // Ignore %YAML, %TAG, etc 56 | "".to_string() 57 | } else if !line.starts_with(" ") && !line.starts_with("'") && !line.is_empty() { 58 | // Replace "Prefab:"(or other) with "type: Prefab" and "content:" 59 | // And add the class_id, file_id, and extra 60 | // TODO: Account for multi-line strings in a better way 61 | format!( 62 | "_class_id: {}\n_file_id: \"{}\"\n_extra: {}\ntype: {}\ncontent:\n", 63 | class_id, 64 | file_id, 65 | match extra { 66 | Some(ref s) => format!("{}\n", s), 67 | None => "".to_string(), 68 | }, 69 | line.replace(":", ""), 70 | ) 71 | } else { 72 | if line.starts_with(" m_Name:") { 73 | m_name_count += 1; 74 | if m_name_count == 1 { 75 | format!("{line}\n") 76 | } else { 77 | let k = m_name_re 78 | .replace(line, format!("m_Name{}:", m_name_count - 1).as_str()); 79 | format!("{k}\n") 80 | } 81 | } else { 82 | // Keep the usual field lines 83 | let k = re.replace(line, "fileID: \"$1\""); 84 | format!("{k}\n") 85 | } 86 | } 87 | }) 88 | .collect::() 89 | } 90 | 91 | //---------------------------------------- 92 | 93 | pub fn asset_meta_yaml_cleanup(yaml: &str) -> String { 94 | let mut file_format_version = 2usize; 95 | let mut guid = "".to_string(); 96 | let mut folder_asset: Option = None; 97 | let mut time_created = "".to_string(); 98 | let mut license_type = "".to_string(); 99 | 100 | let re = Regex::new(r"fileID: ([\-0-9]+)").unwrap(); 101 | 102 | /* 103 | // Temporary fix to allow deserialize of asset meta files 104 | // TODO: Find a better way to do this 105 | 106 | fileFormatVersion: 2 107 | guid: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 108 | labels: 109 | - a 110 | - b 111 | MonoImporter: 112 | ... 113 | */ 114 | let mut inside_labels: bool = false; 115 | let mut labels: Vec = Vec::new(); 116 | 117 | yaml.lines() 118 | .into_iter() 119 | .map(|line| { 120 | if inside_labels && !line.starts_with("-") { 121 | inside_labels = false; 122 | } 123 | 124 | if inside_labels { 125 | labels.push(line.trim().to_string()); 126 | "".to_owned() 127 | } else if line.starts_with("fileFormatVersion:") { 128 | let mut split = line.split(":"); 129 | split.next().unwrap(); 130 | file_format_version = split.next().unwrap().trim().parse().unwrap(); 131 | "".to_owned() 132 | } else if line.starts_with("guid:") { 133 | let mut split = line.split(":"); 134 | split.next().unwrap(); 135 | guid = split.next().unwrap().trim().to_string(); 136 | "".to_owned() 137 | } else if line.starts_with("timeCreated:") { 138 | let mut split = line.split(":"); 139 | split.next().unwrap(); 140 | time_created = split.next().unwrap().trim().to_string(); 141 | "".to_owned() 142 | } else if line.starts_with("licenseType:") { 143 | let mut split = line.split(":"); 144 | split.next().unwrap(); 145 | license_type = split.next().unwrap().trim().to_string(); 146 | "".to_owned() 147 | } else if line.starts_with("folderAsset:") { 148 | folder_asset = if line.ends_with("yes") { 149 | Some(true) 150 | } else { 151 | None 152 | }; 153 | "".to_owned() 154 | } else if line.starts_with("labels:") { 155 | inside_labels = true; 156 | "".to_owned() 157 | } else if !line.starts_with(" ") { 158 | // Replace "DefaultImporter:"(or other) with "type: DefaultImporter" and "content:" 159 | format!("type: {}\ncontent:\n", line.replace(":", "")) 160 | } else { 161 | // Keep the usual field lines 162 | let k = re.replace(line, "fileID: \"$1\""); 163 | format!("{k}\n") 164 | } 165 | }) 166 | .collect::() 167 | .add( 168 | format!( 169 | "fileFormatVersion: {}\nguid: {}\nfolderAsset: {}\n{}", 170 | file_format_version, 171 | guid, 172 | match folder_asset { 173 | Some(ref s) => format!("{}\n", s), 174 | None => "".to_string(), 175 | }, 176 | match labels.len() { 177 | 0 => "".to_string(), 178 | _ => format!("labels:\n{}\n", labels.join("\n")), 179 | }, 180 | ) 181 | .as_str(), 182 | ) 183 | } 184 | 185 | //---------------------------------------- 186 | 187 | pub fn readfile(dir: &str, file: &str) -> io::Result { 188 | fs::read_to_string(format!("{dir}{file}")) 189 | } 190 | 191 | //---------------------------------------- 192 | 193 | pub fn unitypackage_open(filepath: &str) -> Archive> { 194 | let file = File::open(filepath).unwrap(); 195 | let file = GzDecoder::new(file); 196 | Archive::new(file) 197 | } 198 | 199 | //---------------------------------------- 200 | -------------------------------------------------------------------------------- /src/package.rs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | 3 | use std::borrow::BorrowMut; 4 | use std::fs::{self, File}; 5 | use std::io::Read; 6 | use std::path::PathBuf; 7 | 8 | use flate2::read::GzDecoder; 9 | use tar::Archive; 10 | use walkdir::WalkDir; 11 | 12 | //---------------------------------------- 13 | 14 | pub struct Package { 15 | r#type: PackageType, 16 | } 17 | 18 | //---------------------------------------- 19 | 20 | enum PackageType { 21 | Folder(PathBuf), 22 | Tar(PathBuf), 23 | TarGz(PathBuf), 24 | } 25 | 26 | //---------------------------------------- 27 | 28 | enum PackageFileSystemHandle { 29 | Folder(String, Option), 30 | Tar(Archive), 31 | TarGz(Archive>), 32 | } 33 | 34 | //---------------------------------------- 35 | 36 | pub enum PackageEntries<'a> { 37 | Folder( 38 | String, 39 | Option>>>, 40 | ), 41 | Tar(Result, std::io::Error>), 42 | TarGz(Result>, std::io::Error>), 43 | } 44 | 45 | //---------------------------------------- 46 | 47 | pub enum PackageEntry<'a> { 48 | Folder(String, walkdir::DirEntry), 49 | Tar(tar::Entry<'a, File>), 50 | TarGz(tar::Entry<'a, GzDecoder>), 51 | } 52 | 53 | //---------------------------------------- 54 | 55 | pub struct PackageHandle { 56 | handle: PackageFileSystemHandle, 57 | } 58 | 59 | //---------------------------------------- 60 | 61 | impl core::fmt::Display for Package { 62 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 63 | match &self.r#type { 64 | PackageType::Folder(path) => write!(f, "Folder: {}", path.display()), 65 | PackageType::Tar(path) => write!(f, "Tar: {}", path.display()), 66 | PackageType::TarGz(path) => write!(f, "TarGz: {}", path.display()), 67 | } 68 | } 69 | } 70 | 71 | //---------------------------------------- 72 | 73 | impl Package { 74 | pub fn new(path: &str) -> Result { 75 | let path = PathBuf::from(path); 76 | 77 | if path.is_dir() { 78 | Ok(Package { 79 | // canonicalize to fix any path issues 80 | r#type: PackageType::Folder(path.canonicalize().unwrap()), 81 | }) 82 | } else if path.is_file() { 83 | let kind = infer::get_from_path(&path).unwrap().unwrap(); 84 | match kind.mime_type() { 85 | "application/x-tar" => Ok(Package { 86 | r#type: PackageType::Tar(path), 87 | }), 88 | "application/gzip" => Ok(Package { 89 | r#type: PackageType::TarGz(path), 90 | }), 91 | _ => Err("File is not a tar or tar.gz".into()), 92 | } 93 | } else { 94 | Err("Path is not a file or directory".into()) 95 | } 96 | } 97 | 98 | pub fn open(self) -> Result { 99 | match self.r#type { 100 | PackageType::Folder(path) => { 101 | let dir = WalkDir::new(&path); 102 | Ok(PackageHandle { 103 | handle: PackageFileSystemHandle::Folder( 104 | path.to_str().unwrap().to_string(), 105 | Some(dir), 106 | ), 107 | }) 108 | } 109 | PackageType::Tar(path) => { 110 | let file = File::open(&path).unwrap(); 111 | let archive = Archive::new(file); 112 | Ok(PackageHandle { 113 | handle: PackageFileSystemHandle::Tar(archive), 114 | }) 115 | } 116 | PackageType::TarGz(path) => { 117 | let file = File::open(&path).unwrap(); 118 | let file = GzDecoder::new(file); 119 | 120 | let archive = Archive::new(file); 121 | Ok(PackageHandle { 122 | handle: PackageFileSystemHandle::TarGz(archive), 123 | }) 124 | } 125 | } 126 | } 127 | } 128 | 129 | //---------------------------------------- 130 | 131 | impl PackageHandle { 132 | pub fn entries(&mut self) -> PackageEntries { 133 | match self.handle.borrow_mut() { 134 | PackageFileSystemHandle::Folder(path, entries) => PackageEntries::Folder( 135 | path.clone(), 136 | Some(Box::new(entries.take().unwrap().into_iter())), 137 | ), 138 | PackageFileSystemHandle::Tar(archive) => PackageEntries::Tar(archive.entries()), 139 | PackageFileSystemHandle::TarGz(archive) => PackageEntries::TarGz(archive.entries()), 140 | } 141 | } 142 | } 143 | 144 | //---------------------------------------- 145 | 146 | impl<'a> Iterator for PackageEntries<'a> { 147 | type Item = std::io::Result>; 148 | 149 | fn next(&mut self) -> Option>> { 150 | match self.borrow_mut() { 151 | PackageEntries::Folder(path, entries) => { 152 | let next = entries.as_mut().unwrap().next(); 153 | match next { 154 | Some(Ok(entry)) => Some(Ok(PackageEntry::Folder(path.clone(), entry))), 155 | Some(Err(err)) => Some(Err(err.into())), 156 | None => None, 157 | } 158 | } 159 | PackageEntries::Tar(entries) => match entries.as_mut().unwrap().next() { 160 | Some(Ok(entry)) => Some(Ok(PackageEntry::Tar(entry))), 161 | Some(Err(err)) => Some(Err(err)), 162 | None => None, 163 | }, 164 | PackageEntries::TarGz(entries) => match entries.as_mut().unwrap().next() { 165 | Some(Ok(entry)) => Some(Ok(PackageEntry::TarGz(entry))), 166 | Some(Err(err)) => Some(Err(err)), 167 | None => None, 168 | }, 169 | } 170 | } 171 | } 172 | 173 | impl<'a> PackageEntry<'a> { 174 | pub fn size(&self) -> Option { 175 | match self { 176 | PackageEntry::Folder(_path, entry) => Some(entry.metadata().unwrap().len() as usize), 177 | PackageEntry::Tar(entry) => Some(entry.header().size().unwrap() as usize), 178 | PackageEntry::TarGz(entry) => Some(entry.header().size().unwrap() as usize), 179 | } 180 | } 181 | 182 | pub fn path(&self) -> Option { 183 | match self { 184 | PackageEntry::Folder(root_path, entry) => { 185 | let path = entry.path().to_str().unwrap(); 186 | let replace = format!("{}/", root_path); 187 | let path = path.replace(&replace, ""); 188 | // println!("path: {}", path); 189 | Some(PathBuf::from(path)) 190 | } 191 | PackageEntry::Tar(entry) => Some(PathBuf::from(entry.path().unwrap())), 192 | PackageEntry::TarGz(entry) => Some(PathBuf::from(entry.path().unwrap())), 193 | } 194 | } 195 | 196 | pub fn read_to_string(&mut self, buf: &mut String) -> std::io::Result { 197 | match self { 198 | PackageEntry::Folder(_path, entry) => { 199 | let path = entry.path().to_str().unwrap(); 200 | let result = fs::read_to_string(path); 201 | *buf = result.unwrap(); 202 | Ok(buf.len()) 203 | } 204 | PackageEntry::Tar(entry) => entry.read_to_string(buf), 205 | PackageEntry::TarGz(entry) => entry.read_to_string(buf), 206 | } 207 | } 208 | 209 | pub fn read_to_end(&mut self, buf: &mut Vec) -> std::io::Result { 210 | match self { 211 | PackageEntry::Folder(_path, entry) => { 212 | let path = entry.path().to_str().unwrap(); 213 | let result = fs::read(path); 214 | *buf = result.unwrap(); 215 | Ok(buf.len()) 216 | } 217 | PackageEntry::Tar(entry) => entry.read_to_end(buf), 218 | PackageEntry::TarGz(entry) => entry.read_to_end(buf), 219 | } 220 | } 221 | 222 | pub fn guid(&self) -> String { 223 | match self { 224 | PackageEntry::Folder(root_path, entry) => { 225 | let replace = format!("{}/", root_path); 226 | let path = entry.path().to_str().unwrap(); 227 | let path = path.replace(&replace, ""); 228 | let path = path.split("/").collect::>(); 229 | let path = path.first().unwrap(); 230 | path.to_string() 231 | } 232 | PackageEntry::Tar(entry) => { 233 | let path = entry.path().unwrap(); 234 | let path = path.to_str().unwrap(); 235 | let path = path.split("/").collect::>(); 236 | let path = path.first().unwrap(); 237 | path.to_string() 238 | } 239 | PackageEntry::TarGz(entry) => { 240 | let path = entry.path().unwrap(); 241 | let path = path.to_str().unwrap(); 242 | let path = path.split("/").collect::>(); 243 | let path = path.first().unwrap(); 244 | path.to_string() 245 | } 246 | } 247 | } 248 | } 249 | 250 | //---------------------------------------- 251 | -------------------------------------------------------------------------------- /src/commands.rs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | 3 | use std::{ 4 | collections::HashMap, 5 | io::prelude::*, 6 | path::{Path, PathBuf}, 7 | process::{Command, Stdio}, 8 | }; 9 | 10 | use base64::{engine::general_purpose, Engine as _}; 11 | use serde::{Deserialize, Serialize}; 12 | use unitypackage_util; 13 | 14 | use xxhash_rust::xxh64; 15 | 16 | use crate::package; 17 | 18 | //---------------------------------------- 19 | 20 | #[derive(Debug, Serialize)] 21 | struct Dump { 22 | pathname: Option, 23 | content_type: Option, 24 | asset: Option, 25 | asset_meta: Option, 26 | } 27 | 28 | //---------------------------------------- 29 | 30 | fn yaml_matcher(buf: &[u8]) -> bool { 31 | let sig = b"%YAML"; 32 | return buf.len() >= sig.len() && buf[0..sig.len()].cmp(sig) == std::cmp::Ordering::Equal; 33 | } 34 | 35 | //---------------------------------------- 36 | 37 | fn fbx_matcher(buf: &[u8]) -> bool { 38 | let sig = b"Kaydara FBX Binary"; 39 | return buf.len() >= sig.len() && buf[0..sig.len()].cmp(sig) == std::cmp::Ordering::Equal; 40 | } 41 | 42 | //---------------------------------------- 43 | 44 | pub fn deserializer(yaml: &String) -> serde_yaml::Value { 45 | serde_yaml::Deserializer::from_str(&yaml) 46 | .map(|doc| ::deserialize(doc).unwrap()) 47 | .collect() 48 | } 49 | 50 | /* 51 | // This allows it to continue but removes the whole sub-document if it fails to deserialize 52 | // unitypackage_godot needs to be update to handle this before implementing 53 | 54 | serde_yaml::Deserializer::from_str(&yaml) 55 | .map(|doc| { 56 | let res = ::deserialize(doc); 57 | match res { 58 | Ok(res) => res, 59 | Err(_e) => { 60 | // std::io::stderr() 61 | // .write_all(format!("Deserialize Error: {:?}\n", e).as_bytes()) 62 | // .unwrap(); 63 | serde_yaml::Value::String("".to_owned()) 64 | } 65 | } 66 | }) 67 | .collect() 68 | */ 69 | //---------------------------------------- 70 | 71 | pub fn package_contents_dump(package_file: &str, pretty: bool, debug: bool) { 72 | let mut data = HashMap::::new(); 73 | 74 | let mut info = infer::Infer::new(); 75 | info.add("text/yaml", "yaml", yaml_matcher); 76 | info.add("data/fbx", "fbx", fbx_matcher); 77 | // info.add("text/json", "json", json_matcher); 78 | 79 | for file in package::Package::new(package_file) 80 | .unwrap() 81 | .open() 82 | .unwrap() 83 | .entries() 84 | { 85 | let mut file = file.unwrap(); 86 | 87 | let file_path = file.path().unwrap().to_str().unwrap().to_owned(); 88 | let size = file.size().unwrap(); 89 | 90 | let guid = file.guid(); 91 | if guid.len() < 32 { 92 | continue; 93 | } 94 | 95 | let entry = data.entry(guid.clone()).or_insert_with(|| Dump { 96 | pathname: None, 97 | content_type: None, 98 | asset: None, 99 | asset_meta: None, 100 | }); 101 | 102 | // println!("checking pathname"); 103 | if file_path.ends_with("/pathname") { 104 | if debug { 105 | println!("{}/pathname", guid); 106 | } 107 | 108 | let mut s = String::new(); 109 | file.read_to_string(&mut s).unwrap(); 110 | 111 | entry.pathname = Some(s.split("\n").next().unwrap().to_owned()); 112 | } 113 | 114 | // println!("checking asset.meta"); 115 | if file_path.ends_with("/asset.meta") { 116 | if debug { 117 | println!("{}/asset.meta", guid); 118 | } 119 | 120 | let mut yaml = String::new(); 121 | file.read_to_string(&mut yaml).unwrap(); 122 | 123 | let yaml = unitypackage_util::asset_meta_yaml_cleanup(&yaml); 124 | let deserialized = deserializer(&yaml); 125 | 126 | entry.asset_meta = Some(deserialized); 127 | } 128 | 129 | // println!("checking asset"); 130 | if file_path.ends_with("/asset") { 131 | if debug { 132 | println!("{}/asset", guid); 133 | } 134 | 135 | let mut buffer = Vec::with_capacity(size); 136 | file.read_to_end(&mut buffer).unwrap(); 137 | 138 | if let Some(content_type) = info.get(&buffer) { 139 | entry.content_type = Some(content_type.mime_type().to_owned()); 140 | } 141 | 142 | if buffer.starts_with(b"%YAML") { 143 | let yaml = std::str::from_utf8(&buffer).unwrap(); 144 | let yaml = unitypackage_util::asset_yaml_cleanup(&yaml); 145 | let deserialized = deserializer(&yaml); 146 | 147 | entry.asset = Some(deserialized); 148 | } 149 | } 150 | } 151 | 152 | if !debug { 153 | println!("{}", serde_json_to_string(&data, pretty)); 154 | } 155 | } 156 | 157 | //---------------------------------------- 158 | 159 | pub fn package_contents_name(package_file: &str, guid: &str) { 160 | let looking_for = format!("{}/pathname", guid); 161 | 162 | for file in package::Package::new(package_file) 163 | .unwrap() 164 | .open() 165 | .unwrap() 166 | .entries() 167 | { 168 | let mut file = file.unwrap(); 169 | 170 | let file_path = file.path().unwrap().to_str().unwrap().to_owned(); 171 | // let size = file.size().unwrap(); 172 | 173 | if file_path == looking_for { 174 | let mut s = String::new(); 175 | file.read_to_string(&mut s).unwrap(); 176 | 177 | println!("{}", s.split("\n").next().unwrap()); 178 | return; 179 | } 180 | } 181 | 182 | std::io::stderr() 183 | .write_all(format!("Could not find {} in package\n", looking_for).as_bytes()) 184 | .unwrap(); 185 | std::process::exit(exitcode::NOINPUT); 186 | } 187 | 188 | //---------------------------------------- 189 | 190 | pub fn package_contents_list( 191 | package_file: &str, 192 | dir: &Option, 193 | with_guids: bool, 194 | pretty: bool, 195 | ) { 196 | let mut contents = Vec::new(); 197 | 198 | for file in package::Package::new(package_file) 199 | .unwrap() 200 | .open() 201 | .unwrap() 202 | .entries() 203 | { 204 | let mut file = file.unwrap(); 205 | 206 | let file_path = file.path().unwrap().to_str().unwrap().to_owned(); 207 | // let size = file.size().unwrap(); 208 | 209 | let guid = file.guid(); 210 | if guid.len() < 32 { 211 | continue; 212 | } 213 | 214 | if file_path.ends_with("/pathname") { 215 | let mut pathname = String::new(); 216 | file.read_to_string(&mut pathname).unwrap(); 217 | 218 | let mut include = true; 219 | 220 | if let Some(dir) = dir { 221 | let tmp_file_path = format!("{}", pathname.split("\n").next().unwrap()); 222 | 223 | let path = Path::new(&tmp_file_path); 224 | let parent = path.parent().unwrap().to_str().unwrap(); 225 | 226 | //println!("Looking for {} in {} || {}", dir, s, parent); 227 | include = parent == dir; 228 | } 229 | 230 | if include { 231 | contents.push([guid.to_owned(), pathname.split("\n").next().unwrap().to_owned()]); 232 | } 233 | } 234 | } 235 | 236 | contents.sort_by(|a, b| a[1].cmp(&b[1])); 237 | 238 | if with_guids { 239 | print!("{}", serde_json_to_string(&contents, pretty)); 240 | } else { 241 | let output = contents 242 | .iter() 243 | .map(|item| item[1].to_owned()) 244 | .collect::>(); 245 | print!("{}", serde_json_to_string(&output, pretty)); 246 | } 247 | } 248 | 249 | //---------------------------------------- 250 | 251 | fn serde_json_to_string(json: &T, pretty: bool) -> String { 252 | let printer = if pretty { 253 | serde_json::to_string_pretty 254 | } else { 255 | serde_json::to_string 256 | }; 257 | 258 | printer(&json).unwrap() 259 | } 260 | 261 | //---------------------------------------- 262 | 263 | fn yaml_to_json<'a, T: Deserialize<'a> + Serialize>(yaml: &'a str, pretty: bool) -> String { 264 | let output = serde_yaml::Deserializer::from_str(&yaml) 265 | .map(|doc| T::deserialize(doc).unwrap()) 266 | .map(|doc| serde_json_to_string(&doc, pretty)) 267 | .collect::>() 268 | .join(","); 269 | 270 | format!("[{}]]\n", output) 271 | } 272 | 273 | //---------------------------------------- 274 | 275 | pub fn package_file_extract( 276 | package_file: &str, 277 | guid: &str, 278 | _output_file: &Option, 279 | meta: bool, 280 | json: bool, 281 | pretty: bool, 282 | fbx2gltf: bool, 283 | base64: bool, 284 | ) { 285 | let looking_for = format!("{}/{}", guid, if meta { "asset.meta" } else { "asset" }); 286 | 287 | for file in package::Package::new(package_file) 288 | .unwrap() 289 | .open() 290 | .unwrap() 291 | .entries() 292 | { 293 | let mut file = file.unwrap(); 294 | 295 | let file_path = file.path().unwrap().to_str().unwrap().to_owned(); 296 | let size = file.size().unwrap(); 297 | 298 | if file_path == looking_for { 299 | let mut buffer = Vec::with_capacity(size); 300 | file.read_to_end(&mut buffer).unwrap(); 301 | 302 | if json { 303 | let yaml = std::str::from_utf8(&buffer).unwrap(); 304 | if meta { 305 | let yaml = unitypackage_util::asset_meta_yaml_cleanup(&yaml); 306 | print!("{}", yaml_to_json::(&yaml, pretty)); 307 | } else { 308 | let yaml = unitypackage_util::asset_yaml_cleanup(&yaml); 309 | print!("{}", yaml_to_json::(&yaml, pretty)); 310 | } 311 | } else { 312 | if fbx2gltf { 313 | convert_fbx2gltf(&mut buffer, base64).unwrap(); 314 | } else if base64 { 315 | print!("{}", general_purpose::STANDARD.encode(buffer)); 316 | } else { 317 | std::io::stdout().write_all(&buffer).unwrap(); 318 | } 319 | } 320 | 321 | return; 322 | } 323 | } 324 | 325 | std::io::stderr() 326 | .write_all(format!("Could not find {} in package\n", looking_for).as_bytes()) 327 | .unwrap(); 328 | std::process::exit(exitcode::NOINPUT); 329 | } 330 | 331 | //---------------------------------------- 332 | 333 | pub fn convert_fbx2gltf(buf: &mut Vec, base64: bool) -> Result<(), std::io::Error> { 334 | let program = if cfg!(windows) { 335 | "FBX2glTF.exe" 336 | } else { 337 | "./FBX2glTF" 338 | }; 339 | 340 | let process = Command::new(program) 341 | .arg("-IO") 342 | .stdin(Stdio::piped()) 343 | .stdout(Stdio::piped()) 344 | .spawn()?; 345 | 346 | process.stdin.unwrap().write_all(buf)?; 347 | 348 | let mut buffer = Vec::new(); 349 | process.stdout.unwrap().read_to_end(&mut buffer)?; 350 | 351 | if base64 { 352 | print!("{}", general_purpose::STANDARD.encode(buffer)); 353 | return Ok(()); 354 | } 355 | 356 | std::io::stdout().write_all(&buffer)?; 357 | Ok(()) 358 | } 359 | 360 | //---------------------------------------- 361 | 362 | pub fn xx_hash(text: &str) { 363 | print!("{}", xxh64::xxh64(text.as_bytes(), 0) as i64) 364 | } 365 | 366 | //---------------------------------------- 367 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "1.0.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "anstream" 22 | version = "0.3.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450" 25 | dependencies = [ 26 | "anstyle", 27 | "anstyle-parse", 28 | "anstyle-query", 29 | "anstyle-wincon", 30 | "colorchoice", 31 | "is-terminal", 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle" 37 | version = "1.0.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" 40 | 41 | [[package]] 42 | name = "anstyle-parse" 43 | version = "0.2.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" 46 | dependencies = [ 47 | "utf8parse", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle-query" 52 | version = "1.0.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 55 | dependencies = [ 56 | "windows-sys", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-wincon" 61 | version = "1.0.1" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" 64 | dependencies = [ 65 | "anstyle", 66 | "windows-sys", 67 | ] 68 | 69 | [[package]] 70 | name = "autocfg" 71 | version = "1.1.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 74 | 75 | [[package]] 76 | name = "base64" 77 | version = "0.21.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" 80 | 81 | [[package]] 82 | name = "bitflags" 83 | version = "1.3.2" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 86 | 87 | [[package]] 88 | name = "byteorder" 89 | version = "1.4.3" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 92 | 93 | [[package]] 94 | name = "cc" 95 | version = "1.0.79" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 98 | 99 | [[package]] 100 | name = "cfb" 101 | version = "0.7.3" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" 104 | dependencies = [ 105 | "byteorder", 106 | "fnv", 107 | "uuid", 108 | ] 109 | 110 | [[package]] 111 | name = "cfg-if" 112 | version = "1.0.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 115 | 116 | [[package]] 117 | name = "clap" 118 | version = "4.2.5" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "8a1f23fa97e1d1641371b51f35535cb26959b8e27ab50d167a8b996b5bada819" 121 | dependencies = [ 122 | "clap_builder", 123 | "clap_derive", 124 | "once_cell", 125 | ] 126 | 127 | [[package]] 128 | name = "clap_builder" 129 | version = "4.2.5" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "0fdc5d93c358224b4d6867ef1356d740de2303e9892edc06c5340daeccd96bab" 132 | dependencies = [ 133 | "anstream", 134 | "anstyle", 135 | "bitflags", 136 | "clap_lex", 137 | "strsim", 138 | ] 139 | 140 | [[package]] 141 | name = "clap_derive" 142 | version = "4.2.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" 145 | dependencies = [ 146 | "heck", 147 | "proc-macro2", 148 | "quote", 149 | "syn", 150 | ] 151 | 152 | [[package]] 153 | name = "clap_lex" 154 | version = "0.4.1" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" 157 | 158 | [[package]] 159 | name = "colorchoice" 160 | version = "1.0.0" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 163 | 164 | [[package]] 165 | name = "crc32fast" 166 | version = "1.3.2" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 169 | dependencies = [ 170 | "cfg-if", 171 | ] 172 | 173 | [[package]] 174 | name = "errno" 175 | version = "0.3.1" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" 178 | dependencies = [ 179 | "errno-dragonfly", 180 | "libc", 181 | "windows-sys", 182 | ] 183 | 184 | [[package]] 185 | name = "errno-dragonfly" 186 | version = "0.1.2" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 189 | dependencies = [ 190 | "cc", 191 | "libc", 192 | ] 193 | 194 | [[package]] 195 | name = "exitcode" 196 | version = "1.1.2" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193" 199 | 200 | [[package]] 201 | name = "filetime" 202 | version = "0.2.21" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" 205 | dependencies = [ 206 | "cfg-if", 207 | "libc", 208 | "redox_syscall", 209 | "windows-sys", 210 | ] 211 | 212 | [[package]] 213 | name = "flate2" 214 | version = "1.0.25" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" 217 | dependencies = [ 218 | "crc32fast", 219 | "miniz_oxide", 220 | ] 221 | 222 | [[package]] 223 | name = "fnv" 224 | version = "1.0.7" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 227 | 228 | [[package]] 229 | name = "hashbrown" 230 | version = "0.12.3" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 233 | 234 | [[package]] 235 | name = "heck" 236 | version = "0.4.1" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 239 | 240 | [[package]] 241 | name = "hermit-abi" 242 | version = "0.3.1" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 245 | 246 | [[package]] 247 | name = "indexmap" 248 | version = "1.9.3" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 251 | dependencies = [ 252 | "autocfg", 253 | "hashbrown", 254 | ] 255 | 256 | [[package]] 257 | name = "infer" 258 | version = "0.13.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "f551f8c3a39f68f986517db0d1759de85881894fdc7db798bd2a9df9cb04b7fc" 261 | dependencies = [ 262 | "cfb", 263 | ] 264 | 265 | [[package]] 266 | name = "io-lifetimes" 267 | version = "1.0.10" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" 270 | dependencies = [ 271 | "hermit-abi", 272 | "libc", 273 | "windows-sys", 274 | ] 275 | 276 | [[package]] 277 | name = "is-terminal" 278 | version = "0.4.7" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" 281 | dependencies = [ 282 | "hermit-abi", 283 | "io-lifetimes", 284 | "rustix", 285 | "windows-sys", 286 | ] 287 | 288 | [[package]] 289 | name = "itoa" 290 | version = "1.0.6" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 293 | 294 | [[package]] 295 | name = "libc" 296 | version = "0.2.142" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" 299 | 300 | [[package]] 301 | name = "linked-hash-map" 302 | version = "0.5.6" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 305 | 306 | [[package]] 307 | name = "linux-raw-sys" 308 | version = "0.3.6" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c" 311 | 312 | [[package]] 313 | name = "memchr" 314 | version = "2.5.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 317 | 318 | [[package]] 319 | name = "miniz_oxide" 320 | version = "0.6.2" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" 323 | dependencies = [ 324 | "adler", 325 | ] 326 | 327 | [[package]] 328 | name = "once_cell" 329 | version = "1.17.1" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" 332 | 333 | [[package]] 334 | name = "proc-macro2" 335 | version = "1.0.56" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" 338 | dependencies = [ 339 | "unicode-ident", 340 | ] 341 | 342 | [[package]] 343 | name = "quote" 344 | version = "1.0.26" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" 347 | dependencies = [ 348 | "proc-macro2", 349 | ] 350 | 351 | [[package]] 352 | name = "redox_syscall" 353 | version = "0.2.16" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 356 | dependencies = [ 357 | "bitflags", 358 | ] 359 | 360 | [[package]] 361 | name = "regex" 362 | version = "1.8.1" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" 365 | dependencies = [ 366 | "aho-corasick", 367 | "memchr", 368 | "regex-syntax", 369 | ] 370 | 371 | [[package]] 372 | name = "regex-syntax" 373 | version = "0.7.1" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" 376 | 377 | [[package]] 378 | name = "rustix" 379 | version = "0.37.17" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "bc809f704c03a812ac71f22456c857be34185cac691a4316f27ab0f633bb9009" 382 | dependencies = [ 383 | "bitflags", 384 | "errno", 385 | "io-lifetimes", 386 | "libc", 387 | "linux-raw-sys", 388 | "windows-sys", 389 | ] 390 | 391 | [[package]] 392 | name = "ryu" 393 | version = "1.0.13" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 396 | 397 | [[package]] 398 | name = "same-file" 399 | version = "1.0.6" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 402 | dependencies = [ 403 | "winapi-util", 404 | ] 405 | 406 | [[package]] 407 | name = "serde" 408 | version = "1.0.160" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" 411 | dependencies = [ 412 | "serde_derive", 413 | ] 414 | 415 | [[package]] 416 | name = "serde_derive" 417 | version = "1.0.160" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" 420 | dependencies = [ 421 | "proc-macro2", 422 | "quote", 423 | "syn", 424 | ] 425 | 426 | [[package]] 427 | name = "serde_json" 428 | version = "1.0.96" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" 431 | dependencies = [ 432 | "itoa", 433 | "ryu", 434 | "serde", 435 | ] 436 | 437 | [[package]] 438 | name = "serde_yaml" 439 | version = "0.9.21" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" 442 | dependencies = [ 443 | "indexmap", 444 | "itoa", 445 | "ryu", 446 | "serde", 447 | "unsafe-libyaml", 448 | ] 449 | 450 | [[package]] 451 | name = "strsim" 452 | version = "0.10.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 455 | 456 | [[package]] 457 | name = "syn" 458 | version = "2.0.15" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" 461 | dependencies = [ 462 | "proc-macro2", 463 | "quote", 464 | "unicode-ident", 465 | ] 466 | 467 | [[package]] 468 | name = "tar" 469 | version = "0.4.38" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" 472 | dependencies = [ 473 | "filetime", 474 | "libc", 475 | "xattr", 476 | ] 477 | 478 | [[package]] 479 | name = "unicode-ident" 480 | version = "1.0.8" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 483 | 484 | [[package]] 485 | name = "unity-yaml-rust" 486 | version = "0.1.1" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "db5f4dc9b56b2b484047413889a46fe9f39f1758feb0abba982e6aa56ae1f59c" 489 | dependencies = [ 490 | "linked-hash-map", 491 | ] 492 | 493 | [[package]] 494 | name = "unitypackage_util" 495 | version = "0.1.3" 496 | dependencies = [ 497 | "base64", 498 | "clap", 499 | "exitcode", 500 | "flate2", 501 | "infer", 502 | "regex", 503 | "serde", 504 | "serde_json", 505 | "serde_yaml", 506 | "tar", 507 | "unity-yaml-rust", 508 | "walkdir", 509 | "xxhash-rust", 510 | ] 511 | 512 | [[package]] 513 | name = "unsafe-libyaml" 514 | version = "0.2.8" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" 517 | 518 | [[package]] 519 | name = "utf8parse" 520 | version = "0.2.1" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 523 | 524 | [[package]] 525 | name = "uuid" 526 | version = "1.3.2" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2" 529 | 530 | [[package]] 531 | name = "walkdir" 532 | version = "2.3.3" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" 535 | dependencies = [ 536 | "same-file", 537 | "winapi-util", 538 | ] 539 | 540 | [[package]] 541 | name = "winapi" 542 | version = "0.3.9" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 545 | dependencies = [ 546 | "winapi-i686-pc-windows-gnu", 547 | "winapi-x86_64-pc-windows-gnu", 548 | ] 549 | 550 | [[package]] 551 | name = "winapi-i686-pc-windows-gnu" 552 | version = "0.4.0" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 555 | 556 | [[package]] 557 | name = "winapi-util" 558 | version = "0.1.5" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 561 | dependencies = [ 562 | "winapi", 563 | ] 564 | 565 | [[package]] 566 | name = "winapi-x86_64-pc-windows-gnu" 567 | version = "0.4.0" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 570 | 571 | [[package]] 572 | name = "windows-sys" 573 | version = "0.48.0" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 576 | dependencies = [ 577 | "windows-targets", 578 | ] 579 | 580 | [[package]] 581 | name = "windows-targets" 582 | version = "0.48.0" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 585 | dependencies = [ 586 | "windows_aarch64_gnullvm", 587 | "windows_aarch64_msvc", 588 | "windows_i686_gnu", 589 | "windows_i686_msvc", 590 | "windows_x86_64_gnu", 591 | "windows_x86_64_gnullvm", 592 | "windows_x86_64_msvc", 593 | ] 594 | 595 | [[package]] 596 | name = "windows_aarch64_gnullvm" 597 | version = "0.48.0" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 600 | 601 | [[package]] 602 | name = "windows_aarch64_msvc" 603 | version = "0.48.0" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 606 | 607 | [[package]] 608 | name = "windows_i686_gnu" 609 | version = "0.48.0" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 612 | 613 | [[package]] 614 | name = "windows_i686_msvc" 615 | version = "0.48.0" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 618 | 619 | [[package]] 620 | name = "windows_x86_64_gnu" 621 | version = "0.48.0" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 624 | 625 | [[package]] 626 | name = "windows_x86_64_gnullvm" 627 | version = "0.48.0" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 630 | 631 | [[package]] 632 | name = "windows_x86_64_msvc" 633 | version = "0.48.0" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 636 | 637 | [[package]] 638 | name = "xattr" 639 | version = "0.2.3" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" 642 | dependencies = [ 643 | "libc", 644 | ] 645 | 646 | [[package]] 647 | name = "xxhash-rust" 648 | version = "0.8.6" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70" 651 | --------------------------------------------------------------------------------