Add bundles, relinking and repair of updated entries
- bundle: move a binary and its companion files into their own folder, picking the main executable with --main, zenity or kdialog - relink: point an entry at a different executable - repair: follow AppImages that were renamed or replaced by an update, matched by update info, embedded desktop id or version-stripped name Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
296b987e3b
commit
aab55369f6
8 files changed
+766
-57
No files matched your search
+103
-14
@@ -2,7 +2,7 @@ use std::{
|
||||
collections::HashMap,
|
||||
ffi::OsStr,
|
||||
fs::File,
|
||||
io::{BufReader, Read},
|
||||
io::{BufReader, Read, Seek, SeekFrom},
|
||||
path::{Component, Path, PathBuf},
|
||||
};
|
||||
|
||||
@@ -22,13 +22,7 @@ pub struct Embedded {
|
||||
}
|
||||
|
||||
pub fn read(path: &Path, offset: u64) -> Result<Embedded> {
|
||||
let file = BufReader::new(File::open(path)?);
|
||||
let fs = FilesystemReader::from_reader_with_offset(file, offset).with_context(|| {
|
||||
format!(
|
||||
"cannot read the squashfs image embedded in {}",
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
let fs = open(path, offset)?;
|
||||
let image = Image::new(&fs);
|
||||
let desktop = image.root_desktop();
|
||||
let icon_name = desktop.as_ref().and_then(|d| d.get("Icon"));
|
||||
@@ -36,6 +30,100 @@ pub fn read(path: &Path, offset: u64) -> Result<Embedded> {
|
||||
Ok(Embedded { desktop, icon })
|
||||
}
|
||||
|
||||
pub fn desktop_id(path: &Path, offset: u64) -> Option<String> {
|
||||
let fs = open(path, offset).ok()?;
|
||||
let image = Image::new(&fs);
|
||||
let name = image.root_desktop_path()?.file_name()?;
|
||||
Some(name.to_string_lossy().into_owned())
|
||||
}
|
||||
|
||||
pub fn update_info(path: &Path) -> Option<String> {
|
||||
let mut file = File::open(path).ok()?;
|
||||
let mut header = [0u8; 64];
|
||||
file.read_exact(&mut header).ok()?;
|
||||
if !header.starts_with(b"\x7fELF") {
|
||||
return None;
|
||||
}
|
||||
let little_endian = header[5] == 1;
|
||||
let wide = match header[4] {
|
||||
1 => false,
|
||||
2 => true,
|
||||
_ => return None,
|
||||
};
|
||||
let uint = |bytes: &[u8], offset: usize, len: usize| -> Option<u64> {
|
||||
let bytes = bytes.get(offset..offset + len)?;
|
||||
let fold = |acc: u64, &b: &u8| (acc << 8) | u64::from(b);
|
||||
Some(if little_endian {
|
||||
bytes.iter().rev().fold(0, fold)
|
||||
} else {
|
||||
bytes.iter().fold(0, fold)
|
||||
})
|
||||
};
|
||||
let (shoff, shentsize, shnum, shstrndx) = if wide {
|
||||
(
|
||||
uint(&header, 0x28, 8)?,
|
||||
uint(&header, 0x3A, 2)?,
|
||||
uint(&header, 0x3C, 2)?,
|
||||
uint(&header, 0x3E, 2)?,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
uint(&header, 0x20, 4)?,
|
||||
uint(&header, 0x2E, 2)?,
|
||||
uint(&header, 0x30, 2)?,
|
||||
uint(&header, 0x32, 2)?,
|
||||
)
|
||||
};
|
||||
let mut table = vec![0u8; usize::try_from(shentsize * shnum).ok()?];
|
||||
file.seek(SeekFrom::Start(shoff)).ok()?;
|
||||
file.read_exact(&mut table).ok()?;
|
||||
let section = |index: u64| -> Option<(u64, u64, u64)> {
|
||||
let base = usize::try_from(index * shentsize).ok()?;
|
||||
let name = uint(&table, base, 4)?;
|
||||
Some(if wide {
|
||||
(
|
||||
name,
|
||||
uint(&table, base + 0x18, 8)?,
|
||||
uint(&table, base + 0x20, 8)?,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
name,
|
||||
uint(&table, base + 0x10, 4)?,
|
||||
uint(&table, base + 0x14, 4)?,
|
||||
)
|
||||
})
|
||||
};
|
||||
let mut read_at = |offset: u64, size: u64| -> Option<Vec<u8>> {
|
||||
let mut data = vec![0u8; usize::try_from(size.min(1 << 16)).ok()?];
|
||||
file.seek(SeekFrom::Start(offset)).ok()?;
|
||||
file.read_exact(&mut data).ok()?;
|
||||
Some(data)
|
||||
};
|
||||
let (_, names_offset, names_size) = section(shstrndx)?;
|
||||
let names = read_at(names_offset, names_size)?;
|
||||
let (_, offset, size) = (0..shnum).filter_map(section).find(|(name, _, _)| {
|
||||
usize::try_from(*name)
|
||||
.ok()
|
||||
.and_then(|start| names.get(start..))
|
||||
.is_some_and(|rest| rest.starts_with(b".upd_info\0"))
|
||||
})?;
|
||||
let data = read_at(offset, size)?;
|
||||
let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
|
||||
let info = String::from_utf8_lossy(&data[..end]).trim().to_string();
|
||||
(!info.is_empty()).then_some(info)
|
||||
}
|
||||
|
||||
fn open(path: &Path, offset: u64) -> Result<FilesystemReader<'static>> {
|
||||
let file = BufReader::new(File::open(path)?);
|
||||
FilesystemReader::from_reader_with_offset(file, offset).with_context(|| {
|
||||
format!(
|
||||
"cannot read the squashfs image embedded in {}",
|
||||
path.display()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
struct Image<'a, 'b> {
|
||||
fs: &'a FilesystemReader<'b>,
|
||||
nodes: HashMap<PathBuf, &'a InnerNode<SquashfsFileReader>>,
|
||||
@@ -69,14 +157,15 @@ impl<'a, 'b> Image<'a, 'b> {
|
||||
None
|
||||
}
|
||||
|
||||
fn root_desktop(&self) -> Option<DesktopEntry> {
|
||||
let mut candidates: Vec<&PathBuf> = self
|
||||
.nodes
|
||||
fn root_desktop_path(&self) -> Option<&PathBuf> {
|
||||
self.nodes
|
||||
.keys()
|
||||
.filter(|p| p.components().count() == 1 && p.extension() == Some(OsStr::new("desktop")))
|
||||
.collect();
|
||||
candidates.sort();
|
||||
let data = self.read(candidates.first()?)?;
|
||||
.min()
|
||||
}
|
||||
|
||||
fn root_desktop(&self) -> Option<DesktopEntry> {
|
||||
let data = self.read(self.root_desktop_path()?)?;
|
||||
Some(DesktopEntry::parse(&String::from_utf8_lossy(&data)))
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user