├── .gitignore ├── .github ├── dependabot.yaml └── workflows │ ├── auto-assign.yaml │ └── release.yaml ├── squishy ├── src │ ├── error.rs │ ├── appimage.rs │ └── lib.rs ├── Cargo.toml └── README.md ├── Cargo.toml ├── squishy-cli ├── Cargo.toml ├── src │ ├── cli.rs │ ├── appimage.rs │ └── main.rs └── README.md ├── LICENSE ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/auto-assign.yaml: -------------------------------------------------------------------------------- 1 | name: Auto Assign 2 | on: 3 | issues: 4 | types: [opened] 5 | pull_request: 6 | types: [opened] 7 | jobs: 8 | run: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | steps: 14 | - name: 'Auto-assign issue' 15 | uses: pozil/auto-assign-issue@v2 16 | with: 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | assignees: QaidVoid 19 | numOfAssignee: 1 20 | -------------------------------------------------------------------------------- /squishy/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use thiserror::Error; 4 | 5 | #[derive(Error, Debug)] 6 | pub enum SquishyError { 7 | #[error("Failed to find SquashFS magic bytes in the file")] 8 | NoSquashFsFound, 9 | 10 | #[error("IO error: {0}")] 11 | Io(#[from] std::io::Error), 12 | 13 | #[error("SquashFS error: {0}")] 14 | InvalidSquashFS(String), 15 | 16 | #[error("Symlink error: {0}")] 17 | SymlinkError(String), 18 | 19 | #[error("File not found: {0}")] 20 | FileNotFound(PathBuf), 21 | } 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "squishy-cli", 4 | "squishy", 5 | ] 6 | resolver = "2" 7 | 8 | [workspace.package] 9 | version = "0.3.2" 10 | authors = ["Rabindra Dhakal "] 11 | license = "MIT" 12 | edition = "2021" 13 | description = "A convenient high level library for reading SquashFS files" 14 | repository = "https://github.com/pkgforge/squishy-rs" 15 | keywords = ["appimage", "filesystem", "squashfs", "linux"] 16 | 17 | [profile.release] 18 | strip = true 19 | opt-level = "z" 20 | lto = true 21 | codegen-units = 1 22 | panic = "abort" 23 | -------------------------------------------------------------------------------- /squishy-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "squishy-cli" 3 | description = "A simple CLI tool to work with SquashFS files" 4 | version = "0.3.1" 5 | authors.workspace = true 6 | license.workspace = true 7 | edition.workspace = true 8 | repository.workspace = true 9 | keywords.workspace = true 10 | 11 | [[bin]] 12 | name = "squishy" 13 | path = "src/main.rs" 14 | 15 | [dependencies] 16 | squishy = { path = "../squishy", version = "0.3.1", features = ["appimage", "rayon"] } 17 | clap = { version = "4.5.20", features = ["cargo", "derive"] } 18 | goblin = { version = "0.9.2", default-features = false, features = ["elf32", "elf64", "endian_fd", "std"] } 19 | rayon = "1.10.0" 20 | -------------------------------------------------------------------------------- /squishy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "squishy" 3 | version.workspace = true 4 | authors.workspace = true 5 | license.workspace = true 6 | edition.workspace = true 7 | description.workspace = true 8 | repository.workspace = true 9 | keywords.workspace = true 10 | 11 | [lib] 12 | name = "squishy" 13 | path = "src/lib.rs" 14 | 15 | [features] 16 | default = [] 17 | appimage = ["goblin", "rayon"] 18 | rayon = ["dep:rayon"] 19 | 20 | [dependencies] 21 | backhand = "0.18.0" 22 | goblin = { version = "0.9.2", default-features = false, features = ["elf32", "elf64", "endian_fd", "std"], optional = true } 23 | rayon = { version = "1.10.0", optional = true } 24 | thiserror = "2.0.0" 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Rabindra Dhakal 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 | -------------------------------------------------------------------------------- /squishy/README.md: -------------------------------------------------------------------------------- 1 | # 🗜️ Squishy 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | A convenient wrapper around the [backhand](https://github.com/wcampbell0x2a/backhand) library for reading and extracting files from SquashFS filesystems. Provides both a library and CLI tool. 6 | 7 | ## Features 8 | 9 | - Read and extract files from SquashFS filesystems 10 | - Traverse filesystem entries 11 | - Handle symlinks with cycle detection 12 | - Search for files using custom predicates 13 | 14 | ## Usage 15 | 16 | Add this to your `Cargo.toml`: 17 | 18 | ```toml 19 | [dependencies] 20 | squishy = "0.2.1" 21 | ``` 22 | 23 | ### Example 24 | 25 | ```rust 26 | use squishy::{SquashFS, EntryKind}; 27 | use std::path::Path; 28 | 29 | // Open a SquashFS file 30 | let squashfs = SquashFS::from_path(&Path::new("example.squashfs"))?; 31 | 32 | // List all entries 33 | for entry in squashfs.entries() { 34 | println!("{}", entry.path.display()); 35 | } 36 | 37 | // Optionally, parallel read with rayon 38 | use rayon::iter::ParallelIterator; 39 | for entry in squashfs.par_entries() { 40 | println!("{}", entry.path.display()); 41 | } 42 | 43 | // Write file entries to disk 44 | for entry in squashfs.entries() { 45 | if let EntryKind::File(file) = entry.kind { 46 | squashfs.write_file(file, "/path/to/output/file")?; 47 | } 48 | } 49 | 50 | // Read a specific file 51 | // Note: the whole file content will be loaded into memory 52 | let contents = squashfs.read_file("path/to/file.txt")?; 53 | ``` 54 | 55 | ## License 56 | 57 | This project is licensed under the [MIT] License - see the [LICENSE](LICENSE) file for details. 58 | -------------------------------------------------------------------------------- /squishy-cli/src/cli.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::{Parser, Subcommand}; 4 | 5 | #[derive(Parser)] 6 | #[command( 7 | author, 8 | version, 9 | about, 10 | help_template = "{before-help}{name} {version} 11 | {author-with-newline}{about-with-newline} 12 | {usage-heading} {usage} 13 | 14 | {all-args}{after-help}", 15 | arg_required_else_help = true 16 | )] 17 | pub struct Args { 18 | #[clap(subcommand)] 19 | pub command: Commands, 20 | 21 | #[clap(required = false, long, short)] 22 | pub quiet: bool, 23 | } 24 | 25 | #[derive(Subcommand)] 26 | pub enum Commands { 27 | /// AppImage specific tasks 28 | #[command(arg_required_else_help = true)] 29 | #[clap(name = "appimage", alias = "ai")] 30 | AppImage { 31 | /// Path to appimage file 32 | #[arg(required = true)] 33 | file: PathBuf, 34 | 35 | /// Offset 36 | #[arg(required = false, long, short)] 37 | offset: Option, 38 | 39 | /// Filter to apply 40 | #[arg(required = false, long, short)] 41 | filter: Option, 42 | 43 | /// Whether to search for icon 44 | #[arg(required = false, long, short)] 45 | icon: bool, 46 | 47 | /// Whether to search for desktop file 48 | #[arg(required = false, long, short)] 49 | desktop: bool, 50 | 51 | /// Whether to search for appstream file 52 | #[arg(required = false, long, short)] 53 | appstream: bool, 54 | 55 | /// Whether to write files to disk 56 | #[arg(required = false, long, short)] 57 | write: Option>, 58 | 59 | /// Whether to extract the file with the original name from the squashfs inside the AppImage 60 | #[arg(required = false, long = "original-name")] 61 | original_name: bool, 62 | 63 | /// Copy permissions from the squashfs entry 64 | #[arg(required = false, long)] 65 | copy_permissions: bool, 66 | }, 67 | 68 | Unsquashfs { 69 | /// Path to squashfs file 70 | #[arg(required = true)] 71 | file: PathBuf, 72 | 73 | /// Offset 74 | #[arg(required = false, long, short)] 75 | offset: Option, 76 | 77 | /// Whether to write files to disk 78 | #[arg(required = false, long, short)] 79 | write: Option>, 80 | }, 81 | } 82 | -------------------------------------------------------------------------------- /squishy-cli/src/appimage.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{OsStr, OsString}, 3 | fs, 4 | path::Path, 5 | }; 6 | 7 | use squishy::{error::SquishyError, EntryKind, SquashFS, SquashFSEntry}; 8 | 9 | pub type Result = std::result::Result; 10 | 11 | pub fn extract_file>( 12 | squashfs: &SquashFS, 13 | entry: &SquashFSEntry, 14 | output_dir: P, 15 | output_name: Option<&OsStr>, 16 | copy_permissions: bool, 17 | ) -> Result<()> { 18 | if let EntryKind::File(basic_file) = entry.kind { 19 | let file = &entry.path; 20 | let file_name = output_name 21 | .map(|output_name| { 22 | let name_with_extension = file 23 | .extension() 24 | .map(|ext| { 25 | let file_str = file.file_name().unwrap().to_string_lossy(); 26 | if file_str.ends_with("appdata.xml") || file_str.ends_with("metainfo.xml") { 27 | let base_name = if file_str.ends_with("appdata.xml") { 28 | "appdata" 29 | } else { 30 | "metainfo" 31 | }; 32 | format!( 33 | "{}.{}.{}", 34 | output_name.to_string_lossy(), 35 | base_name, 36 | ext.to_string_lossy() 37 | ) 38 | } else { 39 | format!( 40 | "{}.{}", 41 | output_name.to_string_lossy(), 42 | ext.to_string_lossy() 43 | ) 44 | } 45 | }) 46 | .unwrap_or_else(|| file.file_name().unwrap().to_string_lossy().to_string()); 47 | 48 | OsString::from(name_with_extension) 49 | }) 50 | .unwrap_or_else(|| file.file_name().unwrap().to_os_string()); 51 | 52 | fs::create_dir_all(&output_dir)?; 53 | let output_path = output_dir.as_ref().join(file_name); 54 | if copy_permissions { 55 | squashfs.write_file_with_permissions(basic_file, &output_path, entry.header)?; 56 | } else { 57 | squashfs.write_file(basic_file, &output_path)?; 58 | } 59 | println!("Wrote {} to {}", file.display(), output_path.display()); 60 | } 61 | Ok(()) 62 | } 63 | -------------------------------------------------------------------------------- /squishy-cli/README.md: -------------------------------------------------------------------------------- 1 | # 🗜️ Squishy 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | A convenient wrapper around the [backhand](https://github.com/wcampbell0x2a/backhand) library for reading and extracting files from SquashFS filesystems. 6 | 7 | ## Features 8 | 9 | - Extract AppImage resources: 10 | - Icon files (PNG/SVG) 11 | - Desktop entries 12 | - AppStream metadata 13 | - Flexible output options 14 | 15 | ## Installation 16 | 17 | ### From crates.io 18 | 19 | ```bash 20 | cargo install squishy-cli 21 | ``` 22 | 23 | ### From source 24 | 25 | ```bash 26 | git clone https://github.com/pkgforge/squishy-rs 27 | cd squishy-rs 28 | cargo install --path squishy-cli 29 | ``` 30 | 31 | ## Usage 32 | 33 | The CLI tool provides convenient commands for working with AppImage files. 34 | 35 | ### Basic Commands 36 | 37 | ```bash 38 | # Extract icon from an AppImage 39 | squishy appimage path/to/app.AppImage --icon 40 | 41 | # Extract desktop file 42 | squishy appimage path/to/app.AppImage --desktop 43 | 44 | # Extract AppStream metadata 45 | squishy appimage path/to/app.AppImage --appstream 46 | 47 | # Extract and save files to a specific directory 48 | squishy appimage path/to/app.AppImage --icon --write /output/path 49 | 50 | # Extract multiple resources at once 51 | squishy appimage path/to/app.AppImage --icon --desktop --appstream --write 52 | 53 | # Filter path by query 54 | squishy appimage path/to/app.AppImage --filter "squishy" --icon --desktop --appstream --write 55 | 56 | # Provide custom offset (it'd be calculated automatically if not provided) 57 | # Appimage offset can be read using `path/to/app.AppImage --appimage-offset` 58 | squishy appimage path/to/app.AppImage --offset 128128 --icon --desktop --appstream --write 59 | 60 | # The default output file has same name as the provided file. 61 | # Use --original-name to save as the same file name found inside SquashFS 62 | squishy appimage path/to/app.AppImage --icon --write --original-name 63 | 64 | # Extract contents of squashfs to a specific directory 65 | squishy unsquashfs path/to/app.AppImage -w /output/path 66 | ``` 67 | 68 | ### Command Options 69 | 70 | - `--offset`: Custom offset (i.e. the size of ELF) 71 | - `--filter`: Filter the files using provided query 72 | - `--icon`: Extract application icon 73 | - `--desktop`: Extract desktop entry file 74 | - `--appstream`: Extract AppStream metadata 75 | - `--write`: Write files to disk (optional path argument) 76 | 77 | ## License 78 | 79 | This project is licensed under the [MIT] License - see the [LICENSE](LICENSE) file for details. 80 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: squishy release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | publish-binaries: 12 | name: Publish binaries 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | build: 18 | - { 19 | NAME: x86_64-linux, 20 | TOOLCHAIN: stable, 21 | TARGET: x86_64-unknown-linux-musl, 22 | } 23 | - { 24 | NAME: aarch64-linux, 25 | TOOLCHAIN: stable, 26 | TARGET: aarch64-unknown-linux-musl, 27 | } 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | - name: Set the release version 32 | shell: bash 33 | run: echo "RELEASE_VERSION=${GITHUB_REF:11}" >> $GITHUB_ENV 34 | - name: Install dependencies 35 | shell: bash 36 | run: | 37 | sudo apt-get update 38 | sudo apt-get install -y --no-install-recommends \ 39 | --allow-unauthenticated musl-tools b3sum 40 | - name: Install Rust toolchain 41 | uses: actions-rs/toolchain@v1 42 | with: 43 | toolchain: ${{ matrix.build.TOOLCHAIN }} 44 | target: ${{ matrix.build.TARGET }} 45 | override: true 46 | - name: Build 47 | uses: actions-rs/cargo@v1 48 | with: 49 | use-cross: true 50 | command: build 51 | args: --release --locked --target ${{ matrix.build.TARGET }} 52 | - name: Prepare release assets 53 | shell: bash 54 | run: | 55 | mkdir -p release 56 | cp {LICENSE,README.md} release/ 57 | cp "target/${{ matrix.build.TARGET }}/release/squishy" release/ 58 | - name: Create release artifacts 59 | shell: bash 60 | run: | 61 | cp release/squishy squishy-${{ matrix.build.NAME }} 62 | b3sum squishy-${{ matrix.build.NAME }} \ 63 | > squishy-${{ matrix.build.NAME }}.b3sum 64 | tar -czvf squishy-${{ matrix.build.NAME }}.tar.gz \ 65 | release/ 66 | b3sum squishy-${{ matrix.build.NAME }}.tar.gz \ 67 | > squishy-${{ matrix.build.NAME }}.tar.gz.b3sum 68 | - name: Publish to GitHub 69 | uses: svenstaro/upload-release-action@v2 70 | with: 71 | repo_token: ${{ secrets.GITHUB_TOKEN }} 72 | file: squishy-${{ matrix.build.NAME }}* 73 | file_glob: true 74 | overwrite: true 75 | tag: ${{ github.ref }} 76 | release_name: "squishy v${{ env.RELEASE_VERSION }}" 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🗜️ Squishy 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | 6 | A convenient wrapper around the [backhand](https://github.com/wcampbell0x2a/backhand) library for reading and extracting files from SquashFS filesystems. Provides both a library and CLI tool. 7 | 8 | ## Features 9 | 10 | - 📚 **Library Features** 11 | - Read and extract files from SquashFS filesystems 12 | - Traverse filesystem entries 13 | - Handle symlinks with cycle detection 14 | - Search for files using custom predicates 15 | 16 | - 🛠️ **CLI Features** 17 | - Extract AppImage resources: 18 | - Icon files (PNG/SVG) 19 | - Desktop entries 20 | - AppStream metadata 21 | - Flexible output options 22 | 23 | ## Installation 24 | 25 | ### From crates.io 26 | 27 | ```bash 28 | cargo install squishy-cli 29 | ``` 30 | 31 | ### From source 32 | 33 | ```bash 34 | git clone https://github.com/pkgforge/squishy-rs 35 | cd squishy-rs 36 | cargo install --path squishy-cli 37 | ``` 38 | 39 | ## Library Usage 40 | 41 | Add this to your `Cargo.toml`: 42 | 43 | ```toml 44 | [dependencies] 45 | squishy = "0.2.1" 46 | ``` 47 | 48 | ### Example 49 | 50 | ```rust 51 | use squishy::{SquashFS, EntryKind}; 52 | use std::path::Path; 53 | 54 | // Open a SquashFS file 55 | let squashfs = SquashFS::from_path(&Path::new("example.squashfs"))?; 56 | 57 | // List all entries 58 | for entry in squashfs.entries() { 59 | println!("{}", entry.path.display()); 60 | } 61 | 62 | // Optionally, parallel read with rayon 63 | use rayon::iter::ParallelIterator; 64 | for entry in squashfs.par_entries() { 65 | println!("{}", entry.path.display()); 66 | } 67 | 68 | // Write file entries to disk 69 | for entry in squashfs.entries() { 70 | if let EntryKind::File(file) = entry.kind { 71 | squashfs.write_file(file, "/path/to/output/file")?; 72 | } 73 | } 74 | 75 | // Read a specific file 76 | // Note: the whole file content will be loaded into memory 77 | let contents = squashfs.read_file("path/to/file.txt")?; 78 | ``` 79 | 80 | ## CLI Usage 81 | 82 | The CLI tool provides convenient commands for working with AppImage files. 83 | 84 | ### Basic Commands 85 | 86 | ```bash 87 | # Extract icon from an AppImage 88 | squishy appimage path/to/app.AppImage --icon 89 | 90 | # Extract desktop file 91 | squishy appimage path/to/app.AppImage --desktop 92 | 93 | # Extract AppStream metadata 94 | squishy appimage path/to/app.AppImage --appstream 95 | 96 | # Extract and save files to a specific directory 97 | squishy appimage path/to/app.AppImage --icon --write /output/path 98 | 99 | # Extract multiple resources at once 100 | squishy appimage path/to/app.AppImage --icon --desktop --appstream --write 101 | 102 | # Filter path by query 103 | squishy appimage path/to/app.AppImage --filter "squishy" --icon --desktop --appstream --write 104 | 105 | # Provide custom offset (it'd be calculated automatically if not provided) 106 | # Appimage offset can be read using `path/to/app.AppImage --appimage-offset` 107 | squishy appimage path/to/app.AppImage --offset 128128 --icon --desktop --appstream --write 108 | 109 | # Extract contents of squashfs to a specific directory 110 | squishy unsquashfs path/to/app.AppImage -w /output/path 111 | ``` 112 | 113 | ### Command Options 114 | 115 | - `--offset`: Custom offset (i.e. the size of ELF) 116 | - `--filter`: Filter the files using provided query 117 | - `--icon`: Extract application icon 118 | - `--desktop`: Extract desktop entry file 119 | - `--appstream`: Extract AppStream metadata 120 | - `--write`: Write files to disk (optional path argument) 121 | 122 | ## License 123 | 124 | This project is licensed under the [MIT] License - see the [LICENSE](LICENSE) file for details. 125 | -------------------------------------------------------------------------------- /squishy/src/appimage.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::{Read, Seek, SeekFrom}, 4 | path::Path, 5 | }; 6 | 7 | use goblin::elf::Elf; 8 | use rayon::iter::ParallelIterator; 9 | 10 | use crate::{error::SquishyError, EntryKind, SquashFS, SquashFSEntry}; 11 | 12 | pub type Result = std::result::Result; 13 | 14 | /// Get offset for AppImage. This is used by default if no offset is provided. 15 | /// 16 | /// # Arguments 17 | /// * `path` - Path to the appimage file. 18 | /// 19 | /// # Returns 20 | /// Offset of the appimage, or an error if it fails to parse Elf 21 | pub fn get_offset>(path: P) -> std::io::Result { 22 | let mut file = File::open(path)?; 23 | 24 | let mut elf_header_raw = [0; 64]; 25 | file.read_exact(&mut elf_header_raw)?; 26 | 27 | let section_table_offset = u64::from_le_bytes(elf_header_raw[40..48].try_into().unwrap()); 28 | let section_count = u16::from_le_bytes(elf_header_raw[60..62].try_into().unwrap()); 29 | 30 | let section_table_size = section_count as u64 * 64; 31 | let required_bytes = section_table_offset + section_table_size; 32 | 33 | let mut header_data = vec![0; required_bytes as usize]; 34 | file.seek(SeekFrom::Start(0))?; 35 | file.read_exact(&mut header_data)?; 36 | 37 | let elf = Elf::parse(&header_data) 38 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; 39 | 40 | let section_table_end = 41 | elf.header.e_shoff + (elf.header.e_shentsize as u64 * elf.header.e_shnum as u64); 42 | 43 | let last_section_end = elf 44 | .section_headers 45 | .last() 46 | .map(|section| section.sh_offset + section.sh_size) 47 | .unwrap_or(0); 48 | 49 | Ok(section_table_end.max(last_section_end)) 50 | } 51 | 52 | pub struct AppImage<'a> { 53 | filter: Option<&'a str>, 54 | pub squashfs: SquashFS<'a>, 55 | } 56 | 57 | impl<'a> AppImage<'a> { 58 | /// Creates a new AppImage instance 59 | /// 60 | /// # Arguments 61 | /// 62 | /// * `filter` - Filter to apply 63 | /// * `path` - Path to AppImage 64 | /// * `offset` - Offset to seek to 65 | pub fn new>( 66 | filter: Option<&'a str>, 67 | path: &'a P, 68 | offset: Option, 69 | ) -> Result { 70 | let offset = offset.unwrap_or(get_offset(path)?); 71 | let squashfs = SquashFS::from_path_with_offset(path, offset).map_err(|_| { 72 | SquishyError::InvalidSquashFS( 73 | "Couldn't find squashfs. Try providing valid offset.".to_owned(), 74 | ) 75 | })?; 76 | Ok(AppImage { filter, squashfs }) 77 | } 78 | 79 | /// Find icon in AppImage, filtered 80 | /// It looks for icon in order: 81 | /// - DirIcon at AppImage root 82 | /// - Largest png icon in /usr/share/icons 83 | /// - Largest svg icon in /usr/share/icons 84 | /// - Largest png icon in any path 85 | /// - Largest svg icon in any path 86 | /// 87 | /// # Returns 88 | /// A SquashFS entry to the icon, if found 89 | pub fn find_icon(&self) -> Option { 90 | let icon = self 91 | .search_diricon() 92 | .or_else(|| self.find_largest_icon_path()) 93 | .or_else(|| self.find_png_icon()) 94 | .or_else(|| self.find_svg_icon()); 95 | 96 | if let Some(icon) = &icon { 97 | if let EntryKind::Symlink(_) = icon.kind { 98 | let final_entry = self.squashfs.resolve_symlink(icon).unwrap(); 99 | return final_entry; 100 | } 101 | } 102 | icon 103 | } 104 | 105 | /// Find DirIcon at AppImage root 106 | /// 107 | /// # Returns 108 | /// A SquashFS entry to the icon, if found 109 | fn search_diricon(&self) -> Option { 110 | self.squashfs 111 | .par_entries() 112 | .find_first(|entry| entry.path.to_string_lossy() == "/.DirIcon") 113 | } 114 | 115 | /// Helper method to filter paths 116 | /// 117 | /// # Returns 118 | /// boolean stating if the path matches the filter 119 | fn filter_path(&self, path: &str) -> bool { 120 | self.filter 121 | .as_ref() 122 | .map_or(true, |filter| path.contains(filter)) 123 | } 124 | 125 | /// Find largest png (preferred) or svg icon in /usr/share/icons, filtered 126 | /// 127 | /// # Returns 128 | /// A SquashFS entry to the icon, if found 129 | fn find_largest_icon_path(&self) -> Option { 130 | let png_entries = self.squashfs.par_entries().filter(|entry| { 131 | let path = entry.path.to_string_lossy().to_lowercase(); 132 | path.starts_with("/usr/share/icons/") 133 | && self.filter_path(&path) 134 | && path.ends_with(".png") 135 | }); 136 | 137 | if let Some(entry) = png_entries.max_by_key(|entry| entry.size) { 138 | return Some(entry); 139 | } 140 | 141 | self.squashfs.par_entries().find_first(|entry| { 142 | let path = entry.path.to_string_lossy().to_lowercase(); 143 | path.starts_with("/usr/share/icons") 144 | && self.filter_path(&path) 145 | && path.ends_with(".svg") 146 | }) 147 | } 148 | 149 | /// Find largest png icon in AppImage, filtered 150 | /// 151 | /// # Returns 152 | /// A SquashFS entry to the icon, if found 153 | fn find_png_icon(&self) -> Option { 154 | let png_entries = self.squashfs.par_entries().filter(|entry| { 155 | let p = entry.path.to_string_lossy().to_lowercase(); 156 | self.filter_path(&p) && p.ends_with(".png") 157 | }); 158 | if let Some(entry) = png_entries.max_by_key(|entry| entry.size) { 159 | return Some(entry); 160 | } 161 | None 162 | } 163 | 164 | /// Find largest svg icon in AppImage, filtered 165 | /// 166 | /// # Returns 167 | /// A SquashFS entry to the icon, if found 168 | fn find_svg_icon(&self) -> Option { 169 | self.squashfs.par_entries().find_first(|entry| { 170 | let path = entry.path.to_string_lossy().to_lowercase(); 171 | self.filter_path(&path) && path.ends_with(".svg") 172 | }) 173 | } 174 | 175 | /// Find desktop file in AppImage, filtered 176 | /// 177 | /// # Returns 178 | /// A SquashFS entry to the desktop file, if found 179 | pub fn find_desktop(&self) -> Option { 180 | let desktop = self.squashfs.par_entries().find_first(|entry| { 181 | let path = entry.path.to_string_lossy().to_lowercase(); 182 | self.filter_path(&path) && path.ends_with(".desktop") 183 | }); 184 | 185 | if let Some(desktop) = &desktop { 186 | if let EntryKind::Symlink(_) = desktop.kind { 187 | let final_entry = self.squashfs.resolve_symlink(desktop).unwrap(); 188 | return final_entry; 189 | } 190 | } 191 | desktop 192 | } 193 | 194 | /// Find appstream file in AppImage (appdata.xml | metainfo.xml) 195 | /// 196 | /// # Returns 197 | /// A SquashFS entry to the appstream, if found 198 | pub fn find_appstream(&self) -> Option { 199 | let appstream = self.squashfs.par_entries().find_first(|entry| { 200 | let path = entry.path.to_string_lossy().to_lowercase(); 201 | self.filter_path(&path) 202 | && (path.ends_with("appdata.xml") || path.ends_with("metainfo.xml")) 203 | }); 204 | 205 | if let Some(appstream) = &appstream { 206 | if let EntryKind::Symlink(_) = appstream.kind { 207 | let final_entry = self.squashfs.resolve_symlink(appstream).unwrap(); 208 | return final_entry; 209 | } 210 | } 211 | appstream 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /squishy-cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::{self, Permissions}, 3 | os::unix::{self, fs::PermissionsExt}, 4 | }; 5 | 6 | use appimage::extract_file; 7 | use clap::Parser; 8 | use cli::Args; 9 | use rayon::iter::ParallelIterator; 10 | use squishy::{ 11 | appimage::{get_offset, AppImage}, 12 | error::SquishyError, 13 | EntryKind, SquashFS, 14 | }; 15 | 16 | mod appimage; 17 | mod cli; 18 | 19 | macro_rules! log { 20 | ($quiet:expr, $($arg:tt)*) => { 21 | if !$quiet { 22 | println!($($arg)*); 23 | } 24 | }; 25 | } 26 | 27 | macro_rules! elog { 28 | ($quiet:expr, $($arg:tt)*) => { 29 | if !$quiet { 30 | eprintln!($($arg)*); 31 | } 32 | }; 33 | } 34 | 35 | fn main() { 36 | let args = Args::parse(); 37 | 38 | match args.command { 39 | cli::Commands::AppImage { 40 | offset, 41 | filter, 42 | file, 43 | icon, 44 | desktop, 45 | appstream, 46 | write, 47 | original_name, 48 | copy_permissions, 49 | } => { 50 | if file.exists() { 51 | let appimage = match AppImage::new(filter.as_deref(), &file, offset) { 52 | Ok(appimage) => appimage, 53 | Err(e) => { 54 | elog!(args.quiet, "{}", e); 55 | std::process::exit(-1); 56 | } 57 | }; 58 | 59 | let write_path = if let Some(write) = write { 60 | if let Some(path) = write { 61 | Some(path) 62 | } else { 63 | Some(std::env::current_dir().unwrap()) 64 | } 65 | } else { 66 | None 67 | }; 68 | 69 | let output_name = if original_name { 70 | None 71 | } else { 72 | file.file_name() 73 | }; 74 | 75 | if desktop { 76 | if let Some(desktop) = appimage.find_desktop() { 77 | if let Some(ref write_path) = write_path { 78 | extract_file( 79 | &appimage.squashfs, 80 | &desktop, 81 | write_path, 82 | output_name, 83 | copy_permissions, 84 | ) 85 | .unwrap(); 86 | } else { 87 | log!(args.quiet, "Desktop file: {}", desktop.path.display()); 88 | } 89 | } else { 90 | elog!(args.quiet, "No desktop file found."); 91 | }; 92 | } 93 | if icon { 94 | if let Some(icon) = appimage.find_icon() { 95 | if let Some(ref write_path) = write_path { 96 | extract_file( 97 | &appimage.squashfs, 98 | &icon, 99 | write_path, 100 | output_name, 101 | copy_permissions, 102 | ) 103 | .unwrap(); 104 | } else { 105 | log!(args.quiet, "Icon: {}", icon.path.display()); 106 | } 107 | } else { 108 | elog!(args.quiet, "No icon found."); 109 | }; 110 | } 111 | if appstream { 112 | if let Some(appstream) = appimage.find_appstream() { 113 | if let Some(ref write_path) = write_path { 114 | extract_file( 115 | &appimage.squashfs, 116 | &appstream, 117 | write_path, 118 | output_name, 119 | copy_permissions, 120 | ) 121 | .unwrap(); 122 | } else { 123 | log!(args.quiet, "Appstream file: {}", appstream.path.display()); 124 | } 125 | } else { 126 | elog!(args.quiet, "No appstream file found."); 127 | }; 128 | } 129 | } 130 | } 131 | cli::Commands::Unsquashfs { 132 | offset, 133 | file, 134 | write, 135 | } => { 136 | let write_path = if let Some(write) = write { 137 | if let Some(path) = write { 138 | fs::create_dir_all(&path).unwrap(); 139 | Some(path) 140 | } else { 141 | Some(std::env::current_dir().unwrap()) 142 | } 143 | } else { 144 | None 145 | }; 146 | 147 | let offset = offset.unwrap_or(get_offset(&file).unwrap()); 148 | let squashfs = SquashFS::from_path_with_offset(&file, offset) 149 | .map_err(|_| { 150 | SquishyError::InvalidSquashFS( 151 | "Couldn't find squashfs. Try providing valid offset.".to_owned(), 152 | ) 153 | }) 154 | .unwrap(); 155 | 156 | squashfs.par_entries().for_each(|entry| { 157 | if let Some(output_dir) = &write_path { 158 | let file_path = entry.path.strip_prefix("/").unwrap_or(&entry.path); 159 | let output_path = output_dir.join(file_path); 160 | fs::create_dir_all(output_path.parent().unwrap()).unwrap(); 161 | 162 | match entry.kind { 163 | EntryKind::File(basic_file) => { 164 | if output_path.exists() { 165 | return; 166 | } 167 | let _ = squashfs.write_file_with_permissions( 168 | basic_file, 169 | &output_path, 170 | entry.header, 171 | ); 172 | log!( 173 | args.quiet, 174 | "Wrote {} to {}", 175 | entry.path.display(), 176 | output_path.display() 177 | ); 178 | } 179 | EntryKind::Directory => { 180 | if output_path.exists() { 181 | return; 182 | } 183 | fs::create_dir_all(&output_path).unwrap(); 184 | fs::set_permissions( 185 | &output_path, 186 | Permissions::from_mode(u32::from(entry.header.permissions)), 187 | ) 188 | .unwrap(); 189 | log!( 190 | args.quiet, 191 | "Wrote {} to {}", 192 | entry.path.display(), 193 | output_path.display() 194 | ); 195 | } 196 | EntryKind::Symlink(e) => { 197 | if output_path.exists() { 198 | return; 199 | } 200 | let original_path = e.strip_prefix("/").unwrap_or(&e); 201 | let _ = unix::fs::symlink(original_path, &output_path); 202 | log!( 203 | args.quiet, 204 | "Wrote {} to {}", 205 | entry.path.display(), 206 | output_path.display() 207 | ); 208 | } 209 | _ => {} 210 | }; 211 | } else { 212 | log!(args.quiet, "{}", entry.path.display()); 213 | } 214 | }); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /squishy/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashSet, 3 | fs::{self, File, Permissions}, 4 | io::{BufReader, BufWriter, Read, Seek}, 5 | os::unix::fs::PermissionsExt, 6 | path::{Path, PathBuf}, 7 | }; 8 | 9 | use backhand::{kind::Kind, BasicFile, FilesystemReader, InnerNode, NodeHeader}; 10 | use error::SquishyError; 11 | 12 | #[cfg(feature = "rayon")] 13 | use rayon::iter::{IntoParallelIterator, ParallelIterator}; 14 | 15 | #[cfg(feature = "appimage")] 16 | pub mod appimage; 17 | 18 | pub mod error; 19 | 20 | pub type Result = std::result::Result; 21 | 22 | /// The SquashFS struct provides an interface for reading and interacting with a SquashFS filesystem. 23 | /// It wraps a FilesystemReader, which is responsible for reading the contents of the SquashFS file. 24 | pub struct SquashFS<'a> { 25 | reader: FilesystemReader<'a>, 26 | } 27 | 28 | /// The SquashFSEntry struct represents a single file or directory entry within the SquashFS filesystem. 29 | /// It contains information about the path, size, and type of the entry. 30 | #[derive(Debug)] 31 | pub struct SquashFSEntry<'a> { 32 | pub header: NodeHeader, 33 | pub path: PathBuf, 34 | pub size: u32, 35 | pub kind: EntryKind<'a>, 36 | } 37 | 38 | /// The EntryKind enum represents the different types of entries that can be found in the SquashFS filesystem. 39 | #[derive(Debug, Clone, PartialEq, Eq)] 40 | pub enum EntryKind<'a> { 41 | File(&'a BasicFile), 42 | Directory, 43 | Symlink(PathBuf), 44 | Unknown, 45 | } 46 | 47 | impl<'a> SquashFS<'a> { 48 | /// Creates a new SquashFS instance from a BufReader. 49 | /// 50 | /// # Arguments 51 | /// * `reader` - A BufReader that provides access to the SquashFS data. 52 | /// 53 | /// # Returns 54 | /// A SquashFS instance if the SquashFS data is found and valid, or an error if it is not. 55 | pub fn new(mut reader: BufReader, offset: Option) -> Result 56 | where 57 | R: Read + Seek + Send + 'a, 58 | { 59 | let offset = offset.unwrap_or( 60 | Self::find_squashfs_offset(&mut reader).map_err(|_| SquishyError::NoSquashFsFound)?, 61 | ); 62 | let reader = FilesystemReader::from_reader_with_offset(reader, offset) 63 | .map_err(|e| SquishyError::InvalidSquashFS(e.to_string()))?; 64 | 65 | Ok(Self { reader }) 66 | } 67 | 68 | /// Creates a new SquashFS instance from a file path. Tries to find offset automatically. 69 | /// 70 | /// # Arguments 71 | /// * `path` - The path to the SquashFS file. 72 | /// 73 | /// # Returns 74 | /// A SquashFS instance if the SquashFS data is found and valid, or an error if it is not. 75 | pub fn from_path>(path: &'a P) -> Result { 76 | let file = File::open(path).unwrap(); 77 | let reader = BufReader::new(file); 78 | SquashFS::new(reader, None) 79 | } 80 | 81 | /// Creates a new SquashFS instance from a file path. 82 | /// 83 | /// # Arguments 84 | /// * `path` - The path to the SquashFS file. 85 | /// * `offset` - Seek to offset before reading 86 | /// 87 | /// # Returns 88 | /// A SquashFS instance if the SquashFS data is found and valid, or an error if it is not. 89 | pub fn from_path_with_offset>(path: &'a P, offset: u64) -> Result { 90 | let file = File::open(path).unwrap(); 91 | let reader = BufReader::new(file); 92 | SquashFS::new(reader, Some(offset)) 93 | } 94 | 95 | /// Finds the starting offset of the SquashFS data within the input file. 96 | /// 97 | /// # Arguments 98 | /// * `file` - The BufReader that provides access to the input file. 99 | /// 100 | /// # Returns 101 | /// The starting offset of the SquashFS data, or an error if the SquashFS data is not found. 102 | fn find_squashfs_offset(file: &mut BufReader) -> Result 103 | where 104 | R: Read + Seek, 105 | { 106 | let mut magic = [0_u8; 4]; 107 | let kind = Kind::from_target("le_v4_0").unwrap(); 108 | while file.read_exact(&mut magic).is_ok() { 109 | if magic == kind.magic() { 110 | let found = file.stream_position()? - magic.len() as u64; 111 | file.rewind()?; 112 | return Ok(found); 113 | } 114 | } 115 | Err(SquishyError::NoSquashFsFound) 116 | } 117 | 118 | /// Returns an iterator over all the entries in the SquashFS filesystem. 119 | pub fn entries(&self) -> impl Iterator + '_ { 120 | self.reader.files().map(|node| { 121 | let size = match &node.inner { 122 | InnerNode::File(file) => file.basic.file_size, 123 | _ => 0, 124 | }; 125 | 126 | let kind = match &node.inner { 127 | InnerNode::File(file) => EntryKind::File(&file.basic), 128 | InnerNode::Dir(_) => EntryKind::Directory, 129 | InnerNode::Symlink(symlink) => EntryKind::Symlink( 130 | PathBuf::from(format!("/{}", symlink.link.display())).clone(), 131 | ), 132 | _ => EntryKind::Unknown, 133 | }; 134 | 135 | SquashFSEntry { 136 | header: node.header, 137 | path: node.fullpath.clone(), 138 | size, 139 | kind, 140 | } 141 | }) 142 | } 143 | 144 | #[cfg(feature = "rayon")] 145 | /// Returns a parallel iterator over all the entries in the SquashFS filesystem. 146 | pub fn par_entries(&self) -> impl ParallelIterator + '_ { 147 | self.reader 148 | .files() 149 | .map(|node| { 150 | let size = match &node.inner { 151 | InnerNode::File(file) => file.basic.file_size, 152 | _ => 0, 153 | }; 154 | 155 | let kind = match &node.inner { 156 | InnerNode::File(file) => EntryKind::File(&file.basic), 157 | InnerNode::Dir(_) => EntryKind::Directory, 158 | InnerNode::Symlink(symlink) => EntryKind::Symlink( 159 | PathBuf::from(format!("/{}", symlink.link.display())).clone(), 160 | ), 161 | _ => EntryKind::Unknown, 162 | }; 163 | 164 | SquashFSEntry { 165 | header: node.header, 166 | path: node.fullpath.clone(), 167 | size, 168 | kind, 169 | } 170 | }) 171 | .collect::>() 172 | .into_par_iter() 173 | } 174 | 175 | /// Returns an iterator over all the entries in the SquashFS filesystem 176 | /// that match the provided predicate function. 177 | /// 178 | /// # Arguments 179 | /// * `predicate` - A function that takes a &Path and returns a bool, indicating whether the entry should be included. 180 | pub fn find_entries(&self, predicate: F) -> impl Iterator + '_ 181 | where 182 | F: Fn(&Path) -> bool + 'a, 183 | { 184 | self.entries().filter(move |entry| predicate(&entry.path)) 185 | } 186 | 187 | /// Reads the contents of the specified file from the SquashFS filesystem. 188 | /// 189 | /// # Arguments 190 | /// * `path` - The path to the file within the SquashFS filesystem. 191 | /// 192 | /// # Returns 193 | /// The contents of the file as a Vec, or an error if the file is not found. 194 | pub fn read_file>(&self, path: P) -> Result> { 195 | let path = path.as_ref(); 196 | 197 | for node in self.reader.files() { 198 | if node.fullpath == path { 199 | if let InnerNode::File(file) = &node.inner { 200 | let mut reader = self.reader.file(&file.basic).reader().bytes(); 201 | let mut contents = Vec::new(); 202 | 203 | while let Some(Ok(byte)) = reader.next() { 204 | contents.push(byte); 205 | } 206 | 207 | return Ok(contents); 208 | } 209 | } 210 | } 211 | 212 | Err(SquishyError::FileNotFound(path.to_path_buf())) 213 | } 214 | 215 | /// Writes the contents of the specified file from the SquashFS filesystem 216 | /// to the specified destination path. 217 | /// 218 | /// # Arguments 219 | /// * `file` - The basic file within the SquashFS filesystem. 220 | /// * `dest` - The destination path to write the file to. 221 | /// 222 | /// # Returns 223 | /// An empty result, or an error if the file cannot be read or written. 224 | pub fn write_file>(&self, file: &BasicFile, dest: P) -> Result<()> { 225 | let output_file = File::create(dest)?; 226 | let mut writer = BufWriter::with_capacity(file.file_size as usize, &output_file); 227 | let file = self.reader.file(file); 228 | let mut reader = file.reader(); 229 | std::io::copy(&mut reader, &mut writer)?; 230 | Ok(()) 231 | } 232 | 233 | /// Writes the contents of the specified file from the SquashFS filesystem 234 | /// to the specified destination path with permissions. 235 | /// 236 | /// # Arguments 237 | /// * `file` - The basic file within the SquashFS filesystem. 238 | /// * `dest` - The destination path to write the file to. 239 | /// * `header` - Node header containing file information. 240 | /// 241 | /// # Returns 242 | /// An empty result, or an error if the file cannot be read or written. 243 | pub fn write_file_with_permissions>( 244 | &self, 245 | file: &BasicFile, 246 | dest: P, 247 | header: NodeHeader, 248 | ) -> Result<()> { 249 | let output_file = File::create(&dest)?; 250 | let mode = u32::from(header.permissions); 251 | fs::set_permissions(dest, Permissions::from_mode(mode))?; 252 | let mut writer = BufWriter::with_capacity(file.file_size as usize, &output_file); 253 | let file = self.reader.file(file); 254 | let mut reader = file.reader(); 255 | std::io::copy(&mut reader, &mut writer)?; 256 | Ok(()) 257 | } 258 | 259 | /// Resolves the symlink chain starting from the specified entry, 260 | /// returning the final target entry or an error if a cycle is detected. 261 | /// 262 | /// # Arguments 263 | /// * `entry` - The entry to resolve the symlink for. 264 | /// 265 | /// # Returns 266 | /// The final target entry, or None if the entry is not a symlink, or an error if a cycle is detected. 267 | pub fn resolve_symlink(&self, entry: &SquashFSEntry) -> Result> { 268 | match &entry.kind { 269 | EntryKind::Symlink(target) => { 270 | let mut visited = HashSet::new(); 271 | visited.insert(entry.path.clone()); 272 | self.follow_symlink(target, &mut visited) 273 | } 274 | _ => Ok(None), 275 | } 276 | } 277 | 278 | /// Recursively follows symlinks, keeping track of the visited paths 279 | /// to detect and report cycles. 280 | /// 281 | /// # Arguments 282 | /// * `target` - The path to the symlink target. 283 | /// * `visited` - A mutable HashSet to keep track of visited paths. 284 | /// 285 | /// # Returns 286 | /// The final target entry, or an error if a cycle is detected. 287 | fn follow_symlink( 288 | &self, 289 | target: &Path, 290 | visited: &mut HashSet, 291 | ) -> Result> { 292 | if !visited.insert(target.to_path_buf()) { 293 | return Err(SquishyError::SymlinkError("Cyclic symlink detected".into())); 294 | } 295 | 296 | let target_path = target.to_path_buf(); 297 | 298 | if let Some(target_entry) = self.find_entries(move |p| p == target_path).next() { 299 | match &target_entry.kind { 300 | EntryKind::Symlink(next_target) => self.follow_symlink(next_target, visited), 301 | _ => Ok(Some(target_entry)), 302 | } 303 | } else { 304 | Ok(None) 305 | } 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler2" 7 | version = "2.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 10 | 11 | [[package]] 12 | name = "anstream" 13 | version = "0.6.18" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 16 | dependencies = [ 17 | "anstyle", 18 | "anstyle-parse", 19 | "anstyle-query", 20 | "anstyle-wincon", 21 | "colorchoice", 22 | "is_terminal_polyfill", 23 | "utf8parse", 24 | ] 25 | 26 | [[package]] 27 | name = "anstyle" 28 | version = "1.0.10" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 31 | 32 | [[package]] 33 | name = "anstyle-parse" 34 | version = "0.2.6" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 37 | dependencies = [ 38 | "utf8parse", 39 | ] 40 | 41 | [[package]] 42 | name = "anstyle-query" 43 | version = "1.1.2" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 46 | dependencies = [ 47 | "windows-sys", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle-wincon" 52 | version = "3.0.6" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 55 | dependencies = [ 56 | "anstyle", 57 | "windows-sys", 58 | ] 59 | 60 | [[package]] 61 | name = "backhand" 62 | version = "0.18.0" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "8f2fc1bc7bb7fd449e02000cc1592cc63dcdcd61710f8b9efe32bab2d1784603" 65 | dependencies = [ 66 | "deku", 67 | "flate2", 68 | "rustc-hash", 69 | "thiserror 1.0.68", 70 | "tracing", 71 | "xz2", 72 | "zstd", 73 | "zstd-safe", 74 | ] 75 | 76 | [[package]] 77 | name = "bitvec" 78 | version = "1.0.1" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" 81 | dependencies = [ 82 | "funty", 83 | "radium", 84 | "tap", 85 | "wyz", 86 | ] 87 | 88 | [[package]] 89 | name = "cc" 90 | version = "1.1.36" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "baee610e9452a8f6f0a1b6194ec09ff9e2d85dea54432acdae41aa0761c95d70" 93 | dependencies = [ 94 | "jobserver", 95 | "libc", 96 | "shlex", 97 | ] 98 | 99 | [[package]] 100 | name = "cfg-if" 101 | version = "1.0.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 104 | 105 | [[package]] 106 | name = "clap" 107 | version = "4.5.20" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" 110 | dependencies = [ 111 | "clap_builder", 112 | "clap_derive", 113 | ] 114 | 115 | [[package]] 116 | name = "clap_builder" 117 | version = "4.5.20" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" 120 | dependencies = [ 121 | "anstream", 122 | "anstyle", 123 | "clap_lex", 124 | "strsim", 125 | ] 126 | 127 | [[package]] 128 | name = "clap_derive" 129 | version = "4.5.18" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 132 | dependencies = [ 133 | "heck", 134 | "proc-macro2", 135 | "quote", 136 | "syn", 137 | ] 138 | 139 | [[package]] 140 | name = "clap_lex" 141 | version = "0.7.2" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 144 | 145 | [[package]] 146 | name = "colorchoice" 147 | version = "1.0.3" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 150 | 151 | [[package]] 152 | name = "crc32fast" 153 | version = "1.4.2" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 156 | dependencies = [ 157 | "cfg-if", 158 | ] 159 | 160 | [[package]] 161 | name = "crossbeam-deque" 162 | version = "0.8.5" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 165 | dependencies = [ 166 | "crossbeam-epoch", 167 | "crossbeam-utils", 168 | ] 169 | 170 | [[package]] 171 | name = "crossbeam-epoch" 172 | version = "0.9.18" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 175 | dependencies = [ 176 | "crossbeam-utils", 177 | ] 178 | 179 | [[package]] 180 | name = "crossbeam-utils" 181 | version = "0.8.20" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 184 | 185 | [[package]] 186 | name = "darling" 187 | version = "0.20.10" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 190 | dependencies = [ 191 | "darling_core", 192 | "darling_macro", 193 | ] 194 | 195 | [[package]] 196 | name = "darling_core" 197 | version = "0.20.10" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 200 | dependencies = [ 201 | "fnv", 202 | "ident_case", 203 | "proc-macro2", 204 | "quote", 205 | "strsim", 206 | "syn", 207 | ] 208 | 209 | [[package]] 210 | name = "darling_macro" 211 | version = "0.20.10" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 214 | dependencies = [ 215 | "darling_core", 216 | "quote", 217 | "syn", 218 | ] 219 | 220 | [[package]] 221 | name = "deku" 222 | version = "0.17.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "709ade444d53896e60f6265660eb50480dd08b77bfc822e5dcc233b88b0b2fba" 225 | dependencies = [ 226 | "bitvec", 227 | "deku_derive", 228 | "no_std_io", 229 | "rustversion", 230 | ] 231 | 232 | [[package]] 233 | name = "deku_derive" 234 | version = "0.17.0" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "d7534973f93f9de83203e41c8ddd32d230599fa73fa889f3deb1580ccd186913" 237 | dependencies = [ 238 | "darling", 239 | "proc-macro-crate", 240 | "proc-macro2", 241 | "quote", 242 | "syn", 243 | ] 244 | 245 | [[package]] 246 | name = "either" 247 | version = "1.13.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 250 | 251 | [[package]] 252 | name = "equivalent" 253 | version = "1.0.1" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 256 | 257 | [[package]] 258 | name = "flate2" 259 | version = "1.0.34" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" 262 | dependencies = [ 263 | "crc32fast", 264 | "miniz_oxide", 265 | ] 266 | 267 | [[package]] 268 | name = "fnv" 269 | version = "1.0.7" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 272 | 273 | [[package]] 274 | name = "funty" 275 | version = "2.0.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 278 | 279 | [[package]] 280 | name = "goblin" 281 | version = "0.9.2" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "53ab3f32d1d77146981dea5d6b1e8fe31eedcb7013e5e00d6ccd1259a4b4d923" 284 | dependencies = [ 285 | "log", 286 | "plain", 287 | "scroll", 288 | ] 289 | 290 | [[package]] 291 | name = "hashbrown" 292 | version = "0.15.1" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" 295 | 296 | [[package]] 297 | name = "heck" 298 | version = "0.5.0" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 301 | 302 | [[package]] 303 | name = "ident_case" 304 | version = "1.0.1" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 307 | 308 | [[package]] 309 | name = "indexmap" 310 | version = "2.6.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 313 | dependencies = [ 314 | "equivalent", 315 | "hashbrown", 316 | ] 317 | 318 | [[package]] 319 | name = "is_terminal_polyfill" 320 | version = "1.70.1" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 323 | 324 | [[package]] 325 | name = "jobserver" 326 | version = "0.1.32" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 329 | dependencies = [ 330 | "libc", 331 | ] 332 | 333 | [[package]] 334 | name = "libc" 335 | version = "0.2.161" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" 338 | 339 | [[package]] 340 | name = "log" 341 | version = "0.4.22" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 344 | 345 | [[package]] 346 | name = "lzma-sys" 347 | version = "0.1.20" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" 350 | dependencies = [ 351 | "cc", 352 | "libc", 353 | "pkg-config", 354 | ] 355 | 356 | [[package]] 357 | name = "memchr" 358 | version = "2.7.4" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 361 | 362 | [[package]] 363 | name = "miniz_oxide" 364 | version = "0.8.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 367 | dependencies = [ 368 | "adler2", 369 | ] 370 | 371 | [[package]] 372 | name = "no_std_io" 373 | version = "0.6.0" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "7fa5f306a6f2c01b4fd172f29bb46195b1764061bf926c75e96ff55df3178208" 376 | dependencies = [ 377 | "memchr", 378 | ] 379 | 380 | [[package]] 381 | name = "once_cell" 382 | version = "1.20.2" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 385 | 386 | [[package]] 387 | name = "pin-project-lite" 388 | version = "0.2.15" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 391 | 392 | [[package]] 393 | name = "pkg-config" 394 | version = "0.3.31" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 397 | 398 | [[package]] 399 | name = "plain" 400 | version = "0.2.3" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" 403 | 404 | [[package]] 405 | name = "proc-macro-crate" 406 | version = "3.2.0" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" 409 | dependencies = [ 410 | "toml_edit", 411 | ] 412 | 413 | [[package]] 414 | name = "proc-macro2" 415 | version = "1.0.89" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 418 | dependencies = [ 419 | "unicode-ident", 420 | ] 421 | 422 | [[package]] 423 | name = "quote" 424 | version = "1.0.37" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 427 | dependencies = [ 428 | "proc-macro2", 429 | ] 430 | 431 | [[package]] 432 | name = "radium" 433 | version = "0.7.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 436 | 437 | [[package]] 438 | name = "rayon" 439 | version = "1.10.0" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 442 | dependencies = [ 443 | "either", 444 | "rayon-core", 445 | ] 446 | 447 | [[package]] 448 | name = "rayon-core" 449 | version = "1.12.1" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 452 | dependencies = [ 453 | "crossbeam-deque", 454 | "crossbeam-utils", 455 | ] 456 | 457 | [[package]] 458 | name = "rustc-hash" 459 | version = "1.1.0" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 462 | 463 | [[package]] 464 | name = "rustversion" 465 | version = "1.0.18" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" 468 | 469 | [[package]] 470 | name = "scroll" 471 | version = "0.12.0" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" 474 | dependencies = [ 475 | "scroll_derive", 476 | ] 477 | 478 | [[package]] 479 | name = "scroll_derive" 480 | version = "0.12.0" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" 483 | dependencies = [ 484 | "proc-macro2", 485 | "quote", 486 | "syn", 487 | ] 488 | 489 | [[package]] 490 | name = "shlex" 491 | version = "1.3.0" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 494 | 495 | [[package]] 496 | name = "squishy" 497 | version = "0.3.2" 498 | dependencies = [ 499 | "backhand", 500 | "goblin", 501 | "rayon", 502 | "thiserror 2.0.0", 503 | ] 504 | 505 | [[package]] 506 | name = "squishy-cli" 507 | version = "0.3.1" 508 | dependencies = [ 509 | "clap", 510 | "goblin", 511 | "rayon", 512 | "squishy", 513 | ] 514 | 515 | [[package]] 516 | name = "strsim" 517 | version = "0.11.1" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 520 | 521 | [[package]] 522 | name = "syn" 523 | version = "2.0.87" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 526 | dependencies = [ 527 | "proc-macro2", 528 | "quote", 529 | "unicode-ident", 530 | ] 531 | 532 | [[package]] 533 | name = "tap" 534 | version = "1.0.1" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 537 | 538 | [[package]] 539 | name = "thiserror" 540 | version = "1.0.68" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" 543 | dependencies = [ 544 | "thiserror-impl 1.0.68", 545 | ] 546 | 547 | [[package]] 548 | name = "thiserror" 549 | version = "2.0.0" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668" 552 | dependencies = [ 553 | "thiserror-impl 2.0.0", 554 | ] 555 | 556 | [[package]] 557 | name = "thiserror-impl" 558 | version = "1.0.68" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" 561 | dependencies = [ 562 | "proc-macro2", 563 | "quote", 564 | "syn", 565 | ] 566 | 567 | [[package]] 568 | name = "thiserror-impl" 569 | version = "2.0.0" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972" 572 | dependencies = [ 573 | "proc-macro2", 574 | "quote", 575 | "syn", 576 | ] 577 | 578 | [[package]] 579 | name = "toml_datetime" 580 | version = "0.6.8" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 583 | 584 | [[package]] 585 | name = "toml_edit" 586 | version = "0.22.22" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 589 | dependencies = [ 590 | "indexmap", 591 | "toml_datetime", 592 | "winnow", 593 | ] 594 | 595 | [[package]] 596 | name = "tracing" 597 | version = "0.1.40" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 600 | dependencies = [ 601 | "pin-project-lite", 602 | "tracing-attributes", 603 | "tracing-core", 604 | ] 605 | 606 | [[package]] 607 | name = "tracing-attributes" 608 | version = "0.1.27" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 611 | dependencies = [ 612 | "proc-macro2", 613 | "quote", 614 | "syn", 615 | ] 616 | 617 | [[package]] 618 | name = "tracing-core" 619 | version = "0.1.32" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 622 | dependencies = [ 623 | "once_cell", 624 | ] 625 | 626 | [[package]] 627 | name = "unicode-ident" 628 | version = "1.0.13" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 631 | 632 | [[package]] 633 | name = "utf8parse" 634 | version = "0.2.2" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 637 | 638 | [[package]] 639 | name = "windows-sys" 640 | version = "0.59.0" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 643 | dependencies = [ 644 | "windows-targets", 645 | ] 646 | 647 | [[package]] 648 | name = "windows-targets" 649 | version = "0.52.6" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 652 | dependencies = [ 653 | "windows_aarch64_gnullvm", 654 | "windows_aarch64_msvc", 655 | "windows_i686_gnu", 656 | "windows_i686_gnullvm", 657 | "windows_i686_msvc", 658 | "windows_x86_64_gnu", 659 | "windows_x86_64_gnullvm", 660 | "windows_x86_64_msvc", 661 | ] 662 | 663 | [[package]] 664 | name = "windows_aarch64_gnullvm" 665 | version = "0.52.6" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 668 | 669 | [[package]] 670 | name = "windows_aarch64_msvc" 671 | version = "0.52.6" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 674 | 675 | [[package]] 676 | name = "windows_i686_gnu" 677 | version = "0.52.6" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 680 | 681 | [[package]] 682 | name = "windows_i686_gnullvm" 683 | version = "0.52.6" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 686 | 687 | [[package]] 688 | name = "windows_i686_msvc" 689 | version = "0.52.6" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 692 | 693 | [[package]] 694 | name = "windows_x86_64_gnu" 695 | version = "0.52.6" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 698 | 699 | [[package]] 700 | name = "windows_x86_64_gnullvm" 701 | version = "0.52.6" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 704 | 705 | [[package]] 706 | name = "windows_x86_64_msvc" 707 | version = "0.52.6" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 710 | 711 | [[package]] 712 | name = "winnow" 713 | version = "0.6.20" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" 716 | dependencies = [ 717 | "memchr", 718 | ] 719 | 720 | [[package]] 721 | name = "wyz" 722 | version = "0.5.1" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" 725 | dependencies = [ 726 | "tap", 727 | ] 728 | 729 | [[package]] 730 | name = "xz2" 731 | version = "0.1.7" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" 734 | dependencies = [ 735 | "lzma-sys", 736 | ] 737 | 738 | [[package]] 739 | name = "zstd" 740 | version = "0.13.2" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" 743 | dependencies = [ 744 | "zstd-safe", 745 | ] 746 | 747 | [[package]] 748 | name = "zstd-safe" 749 | version = "7.2.1" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" 752 | dependencies = [ 753 | "zstd-sys", 754 | ] 755 | 756 | [[package]] 757 | name = "zstd-sys" 758 | version = "2.0.13+zstd.1.5.6" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" 761 | dependencies = [ 762 | "cc", 763 | "pkg-config", 764 | ] 765 | --------------------------------------------------------------------------------