pub mod info;
pub mod version;
pub use info::OpcodeInfo;
pub use version::{NsisVersion, ParkSubVersion};
pub const EW_INVALID_OPCODE: i32 = 0;
pub const EW_RET: i32 = 1;
pub const EW_NOP: i32 = 2;
pub const EW_ABORT: i32 = 3;
pub const EW_QUIT: i32 = 4;
pub const EW_CALL: i32 = 5;
pub const EW_UPDATETEXT: i32 = 6;
pub const EW_SLEEP: i32 = 7;
pub const EW_BRINGTOFRONT: i32 = 8;
pub const EW_CHDETAILSVIEW: i32 = 9;
pub const EW_SETFILEATTRIBUTES: i32 = 10;
pub const EW_CREATEDIR: i32 = 11;
pub const EW_IFFILEEXISTS: i32 = 12;
pub const EW_SETFLAG: i32 = 13;
pub const EW_IFFLAG: i32 = 14;
pub const EW_GETFLAG: i32 = 15;
pub const EW_RENAME: i32 = 16;
pub const EW_GETFULLPATHNAME: i32 = 17;
pub const EW_SEARCHPATH: i32 = 18;
pub const EW_GETTEMPFILENAME: i32 = 19;
pub const EW_EXTRACTFILE: i32 = 20;
pub const EW_DELETEFILE: i32 = 21;
pub const EW_MESSAGEBOX: i32 = 22;
pub const EW_RMDIR: i32 = 23;
pub const EW_STRLEN: i32 = 24;
pub const EW_ASSIGNVAR: i32 = 25;
pub const EW_STRCMP: i32 = 26;
pub const EW_READENVSTR: i32 = 27;
pub const EW_INTCMP: i32 = 28;
pub const EW_INTOP: i32 = 29;
pub const EW_INTFMT: i32 = 30;
pub const EW_PUSHPOP: i32 = 31;
pub const EW_FINDWINDOW: i32 = 32;
pub const EW_SENDMESSAGE: i32 = 33;
pub const EW_ISWINDOW: i32 = 34;
pub const EW_GETDLGITEM: i32 = 35;
pub const EW_SETCTLCOLORS: i32 = 36;
pub const EW_LOADANDSETIMAGE: i32 = 37;
pub const EW_CREATEFONT: i32 = 38;
pub const EW_SHOWWINDOW: i32 = 39;
pub const EW_SHELLEXEC: i32 = 40;
pub const EW_EXECUTE: i32 = 41;
pub const EW_GETFILETIME: i32 = 42;
pub const EW_GETDLLVERSION: i32 = 43;
pub const EW_REGISTERDLL: i32 = 44;
pub const EW_CREATESHORTCUT: i32 = 45;
pub const EW_COPYFILES: i32 = 46;
pub const EW_REBOOT: i32 = 47;
pub const EW_WRITEINI: i32 = 48;
pub const EW_READINISTR: i32 = 49;
pub const EW_DELREG: i32 = 50;
pub const EW_WRITEREG: i32 = 51;
pub const EW_READREGSTR: i32 = 52;
pub const EW_REGENUM: i32 = 53;
pub const EW_FCLOSE: i32 = 54;
pub const EW_FOPEN: i32 = 55;
pub const EW_FPUTS: i32 = 56;
pub const EW_FGETS: i32 = 57;
pub const EW_FSEEK: i32 = 58;
pub const EW_FINDCLOSE: i32 = 59;
pub const EW_FINDNEXT: i32 = 60;
pub const EW_FINDFIRST: i32 = 61;
pub const EW_WRITEUNINSTALLER: i32 = 62;
pub const EW_LOG: i32 = 63;
pub const EW_SECTIONSET: i32 = 64;
pub const EW_INSTTYPESET: i32 = 65;
pub const EW_GETOSINFO: i32 = 66;
pub const EW_RESERVEDOPCODE: i32 = 67;
pub const EW_LOCKWINDOW: i32 = 68;
pub const EW_FPUTWS: i32 = 69;
pub const EW_FGETWS: i32 = 70;
pub fn normalize_park_opcode(raw: u32, sub: ParkSubVersion) -> u32 {
let mut a = raw;
if a < EW_REGISTERDLL as u32 {
return a;
}
if matches!(sub, ParkSubVersion::Park2 | ParkSubVersion::Park3) {
if a == EW_REGISTERDLL as u32 {
return raw;
}
a = a.saturating_sub(1);
}
if sub == ParkSubVersion::Park3 {
if a == EW_REGISTERDLL as u32 {
return raw; }
a = a.saturating_sub(1);
}
if a >= EW_FSEEK as u32 {
if a == EW_FSEEK as u32 {
return EW_FPUTWS as u32;
}
if a == (EW_FSEEK as u32).saturating_add(1) {
return EW_FGETWS as u32;
}
a = a.saturating_sub(2);
}
a
}
pub fn detect_park_sub_version(
header_data: &[u8],
entry_block_offset: usize,
entry_count: usize,
) -> ParkSubVersion {
use crate::nsis::entry::Entry;
use crate::util::read_i32_le;
let base = EW_WRITEUNINSTALLER;
let max_raw = base + 4;
let mut mask: u32 = 0;
for i in 0..entry_count {
let Some(offset) = i
.checked_mul(Entry::SIZE)
.and_then(|n| n.checked_add(entry_block_offset))
else {
break;
};
let Some(end) = offset.checked_add(Entry::SIZE) else {
break;
};
if end > header_data.len() {
break;
}
let raw_cmd = read_i32_le(header_data, offset);
if raw_cmd < base || raw_cmd > max_raw {
continue;
}
let p0 = read_i32_le(header_data, offset.saturating_add(4));
let p3 = read_i32_le(header_data, offset.saturating_add(16));
let p4 = read_i32_le(header_data, offset.saturating_add(20));
let p5 = read_i32_le(header_data, offset.saturating_add(24));
if p4 != 0 || p5 != 0 || p0 <= 1 || p3 <= 1 {
continue;
}
let num_inserts = raw_cmd.saturating_sub(base) as u32;
mask |= 1_u32.checked_shl(num_inserts).unwrap_or(0);
}
match mask {
m if m & (1 << 4) != 0 => ParkSubVersion::Park3,
m if m & (1 << 3) != 0 => ParkSubVersion::Park2,
_ => ParkSubVersion::Park1,
}
}
pub fn lookup(version: NsisVersion, which: u32) -> Option<&'static OpcodeInfo> {
let table: &[OpcodeInfo] = match version {
NsisVersion::V2 => &info::OPCODES_NSIS2,
NsisVersion::V3 => info::OPCODES_NSIS3,
NsisVersion::V1 | NsisVersion::Park => &info::OPCODES_NSIS2,
};
table.get(which as usize)
}
pub fn lookup_normalized(
version: NsisVersion,
which: u32,
park_sub: Option<ParkSubVersion>,
) -> Option<&'static OpcodeInfo> {
let normalized = match (version, park_sub) {
(NsisVersion::Park, Some(sub)) => normalize_park_opcode(which, sub),
_ => which,
};
lookup(version, normalized)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lookup_valid_opcode() {
let info = lookup(NsisVersion::V2, 0);
assert!(info.is_some());
assert_eq!(info.unwrap().mnemonic, "EW_INVALID_OPCODE");
}
#[test]
fn lookup_ret() {
let info = lookup(NsisVersion::V2, 1).unwrap();
assert_eq!(info.mnemonic, "EW_RET");
}
#[test]
fn lookup_out_of_range() {
assert!(lookup(NsisVersion::V2, 999).is_none());
}
#[test]
fn lookup_v3() {
let info = lookup(NsisVersion::V3, 0).unwrap();
assert_eq!(info.mnemonic, "EW_INVALID_OPCODE");
}
}