use crate::{error::Error, util::read::Reader};
#[derive(Clone, Debug)]
pub struct Version {
pub a: u8,
pub b: u8,
pub c: u8,
pub d: u8,
pub flags: VersionFlags,
pub raw_marker: [u8; 64],
}
impl Version {
#[must_use]
pub fn at_least(&self, a: u8, b: u8, c: u8) -> bool {
(self.a, self.b, self.c) >= (a, b, c)
}
#[must_use]
pub fn at_least_4(&self, a: u8, b: u8, c: u8, d: u8) -> bool {
(self.a, self.b, self.c, self.d) >= (a, b, c, d)
}
#[must_use]
pub fn is_unicode(&self) -> bool {
self.flags.contains(VersionFlags::UNICODE)
}
#[must_use]
pub fn is_isx(&self) -> bool {
self.flags.contains(VersionFlags::ISX)
}
#[must_use]
pub fn is_16bit(&self) -> bool {
self.flags.contains(VersionFlags::BITS16)
}
#[must_use]
pub fn marker_str(&self) -> &str {
let end = self
.raw_marker
.iter()
.position(|&b| b == 0)
.unwrap_or(self.raw_marker.len());
let visible = self.raw_marker.get(..end).unwrap_or(&[]);
core::str::from_utf8(visible).unwrap_or("")
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Variant {
Stock,
Isx,
Legacy1210,
BlackBox,
Gog,
GogGalaxy,
}
stable_name_enum!(Variant, {
Self::Stock => "stock",
Self::Isx => "isx",
Self::Legacy1210 => "legacy_1_2_10",
Self::BlackBox => "blackbox",
Self::Gog => "gog",
Self::GogGalaxy => "gog_galaxy",
});
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct VersionFlags: u8 {
const BITS16 = 1 << 0;
const UNICODE = 1 << 1;
const ISX = 1 << 2;
}
}
const STOCK_PREFIX: &[u8] = b"Inno Setup Setup Data (";
const ISX_PREFIX: &[u8] = b"My Inno Setup Extensions Setup Data (";
const LEGACY_1210_16: &[u8] = b"i1.2.10--16\x1a";
const LEGACY_1210_32: &[u8] = b"i1.2.10--32\x1a";
pub(crate) fn parse_marker(marker: &[u8; 64]) -> Result<(Version, Variant), Error> {
if let Some(v) = parse_legacy_1210(marker) {
return Ok((v, Variant::Legacy1210));
}
if let Some(v) = parse_modern(marker, ISX_PREFIX, VersionFlags::ISX) {
return Ok((v, Variant::Isx));
}
if let Some(v) = parse_modern(marker, STOCK_PREFIX, VersionFlags::empty()) {
return Ok((v, Variant::Stock));
}
Err(Error::UnsupportedVersion { marker: *marker })
}
fn parse_legacy_1210(marker: &[u8; 64]) -> Option<Version> {
let prefix = marker.get(..LEGACY_1210_16.len())?;
if prefix == LEGACY_1210_16 {
return Some(Version {
a: 1,
b: 2,
c: 10,
d: 0,
flags: VersionFlags::BITS16,
raw_marker: *marker,
});
}
if prefix == LEGACY_1210_32 {
return Some(Version {
a: 1,
b: 2,
c: 10,
d: 0,
flags: VersionFlags::empty(),
raw_marker: *marker,
});
}
None
}
fn parse_modern(marker: &[u8; 64], prefix: &[u8], extra_flags: VersionFlags) -> Option<Version> {
if !marker.starts_with(prefix) {
return None;
}
let after_prefix = marker.get(prefix.len()..)?;
let close = after_prefix.iter().position(|&b| b == b')')?;
let inside = after_prefix.get(..close)?;
let (a, b, c, d) = parse_dotted(inside)?;
let mut flags = extra_flags;
let after_close = after_prefix.get(close.checked_add(1)?..).unwrap_or(&[]);
if has_unicode_flag(after_close) {
flags |= VersionFlags::UNICODE;
}
Some(Version {
a,
b,
c,
d,
flags,
raw_marker: *marker,
})
}
fn parse_dotted(s: &[u8]) -> Option<(u8, u8, u8, u8)> {
let core = match s.last().copied() {
Some(b'a') => s.get(..s.len().checked_sub(1)?)?,
_ => s,
};
let mut parts = core.split(|&b| b == b'.');
let a = parse_u8(parts.next()?)?;
let b = parse_u8(parts.next()?)?;
let c = parse_u8(parts.next()?)?;
let d = match parts.next() {
Some(p) => parse_u8(p)?,
None => 0,
};
if parts.next().is_some() {
return None;
}
Some((a, b, c, d))
}
fn parse_u8(bytes: &[u8]) -> Option<u8> {
if bytes.is_empty() {
return None;
}
let mut value: u32 = 0;
for &byte in bytes {
if !byte.is_ascii_digit() {
return None;
}
let digit = u32::from(byte.saturating_sub(b'0'));
value = value.checked_mul(10)?.checked_add(digit)?;
}
if value > u32::from(u8::MAX) {
return None;
}
Some(value as u8)
}
fn has_unicode_flag(after_close: &[u8]) -> bool {
let mut i = 0usize;
while let Some(&b) = after_close.get(i) {
if b != b' ' {
break;
}
i = i.saturating_add(1);
}
let tail = after_close.get(i..).unwrap_or(&[]);
matches!(tail.get(..3), Some(b"(u)" | b"(U)"))
}
pub(crate) fn read_marker(setup0: &[u8]) -> Result<(Version, Variant), Error> {
let mut r = Reader::new(setup0);
let marker = r.array::<64>("SetupID marker")?;
parse_marker(&marker)
}
#[cfg(test)]
mod tests {
use super::*;
fn pad_marker(s: &[u8]) -> [u8; 64] {
let mut out = [0u8; 64];
let take = s.len().min(out.len());
out[..take].copy_from_slice(&s[..take]);
out
}
#[test]
fn stock_marker_with_unicode() {
let m = pad_marker(b"Inno Setup Setup Data (6.4.0.1) (u)");
let (v, variant) = parse_marker(&m).unwrap();
assert_eq!((v.a, v.b, v.c, v.d), (6, 4, 0, 1));
assert!(v.is_unicode());
assert!(!v.is_isx());
assert_eq!(variant, Variant::Stock);
assert_eq!(v.marker_str(), "Inno Setup Setup Data (6.4.0.1) (u)");
}
#[test]
fn stock_marker_no_qualifier() {
let m = pad_marker(b"Inno Setup Setup Data (6.1.0) (u)");
let (v, _) = parse_marker(&m).unwrap();
assert_eq!((v.a, v.b, v.c, v.d), (6, 1, 0, 0));
assert!(v.is_unicode());
}
#[test]
fn isx_marker() {
let m = pad_marker(b"My Inno Setup Extensions Setup Data (3.0.4)");
let (v, variant) = parse_marker(&m).unwrap();
assert_eq!((v.a, v.b, v.c, v.d), (3, 0, 4, 0));
assert!(v.is_isx());
assert!(!v.is_unicode());
assert_eq!(variant, Variant::Isx);
}
#[test]
fn legacy_1210_32() {
let m = pad_marker(b"i1.2.10--32\x1a");
let (v, variant) = parse_marker(&m).unwrap();
assert_eq!((v.a, v.b, v.c), (1, 2, 10));
assert!(!v.is_16bit());
assert_eq!(variant, Variant::Legacy1210);
}
#[test]
fn legacy_1210_16() {
let m = pad_marker(b"i1.2.10--16\x1a");
let (v, _) = parse_marker(&m).unwrap();
assert!(v.is_16bit());
}
#[test]
fn marker_with_trailing_a() {
let m = pad_marker(b"Inno Setup Setup Data (5.5.7a) (u)");
let (v, _) = parse_marker(&m).unwrap();
assert_eq!((v.a, v.b, v.c, v.d), (5, 5, 7, 0));
}
#[test]
fn unrecognized_marker_is_unsupported() {
let m = pad_marker(b"Some Other Installer 1.2.3");
let err = parse_marker(&m).unwrap_err();
assert!(matches!(err, Error::UnsupportedVersion { .. }));
}
#[test]
fn at_least_compares_first_three_components() {
let m = pad_marker(b"Inno Setup Setup Data (6.4.0.1) (u)");
let (v, _) = parse_marker(&m).unwrap();
assert!(v.at_least(6, 4, 0));
assert!(v.at_least(5, 1, 5));
assert!(!v.at_least(7, 0, 0));
assert!(v.at_least_4(6, 4, 0, 1));
assert!(!v.at_least_4(6, 4, 0, 2));
}
}