nsis-plugin 0.1.1

Write NSIS plug-ins in Rust, correct across every NSIS build variant
Documentation
//! Raw FFI declarations, transcribed from the NSIS 3.12 sources.
//!
//! Everything here is a faithful mirror of `Source/exehead/api.h` and
//! `Contrib/ExDLL/pluginapi.h`. Field order matters — do not reorder.

use core::ffi::{c_int, c_void};

use crate::tchar::Tchar;

/// Installer's parent window.
pub type Hwnd = *mut c_void;

/// Module handle of the loaded plug-in DLL.
pub type Hmodule = *mut c_void;

/// Plug-in ABI version introduced in NSIS 2.42.
///
/// Version checks must use `>=`, per the comment in `api.h`.
pub const NSISPIAPIVER_1_0: c_int = 0x0001_0000;

/// The most recent plug-in ABI version this crate knows about.
pub const NSISPIAPIVER_CURR: c_int = NSISPIAPIVER_1_0;

/// Last message a plug-in receives: do final cleanup.
pub const NSPIM_UNLOAD: c_int = 0;

/// Sent after `.onGUIEnd`.
pub const NSPIM_GUIUNLOAD: c_int = 1;

/// The signature of every plug-in export, as generated by
/// [`nsis_fn!`](crate::nsis_fn).
///
/// The trailing ellipsis in the C declaration makes the export `cdecl`, not
/// `stdcall` — the `NSISCALL` helpers in `pluginapi.h` are stdcall, the export
/// itself is not. Rust's `extern "C"` is cdecl on i686, so this lines up.
pub type Export =
	unsafe extern "C" fn(Hwnd, c_int, *mut Tchar, *mut *mut StackNode, *mut ExtraParameters);

/// A plug-in callback registered through [`ExtraParameters::register_plugin_callback`].
///
/// `api.h` specifies this is always `__cdecl`, "for future expansion
/// possibilities" — note that it is *not* `NSISCALL`. Return `0` for unknown
/// messages.
pub type NsisPluginCallback = unsafe extern "C" fn(c_int) -> usize;

/// Installer state exposed to plug-ins, mirroring `exec_flags_t`.
#[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct ExecFlags {
	/// `SetAutoClose`.
	pub autoclose: c_int,
	/// `SetShellVarContext`: user context = 0, machine context = 1.
	pub all_user_var: c_int,
	/// `IfErrors` / `ClearErrors` / `SetErrors`.
	pub exec_error: c_int,
	/// `IfAbort`.
	pub abort: c_int,
	/// `IfRebootFlag` / `SetRebootFlag`.
	pub exec_reboot: c_int,
	/// Set once a reboot has been requested.
	pub reboot_called: c_int,
	/// Deprecated (`XXX_cur_insttype`). Do not use.
	pub xxx_cur_insttype: c_int,
	/// Plug-in ABI version. Only meaningful from NSIS 2.42 onward.
	pub plugin_api_version: c_int,
	/// `IfSilent` / `SetSilent`.
	pub silent: c_int,
	/// `GetInstDirError`.
	pub instdir_error: c_int,
	/// `IfRtlLanguage`: 1 when `$LANGUAGE` is a right-to-left language.
	pub rtl: c_int,
	/// `SetErrorLevel`.
	pub errlvl: c_int,
	/// `SetRegView`: default view = 0, alternative view = `KEY_WOW64_*`.
	pub alter_reg_view: c_int,
	/// `SetDetailsPrint`.
	pub status_update: c_int,
}

/// The `extra_parameters` block, fifth argument to every plug-in export.
#[repr(C)]
pub struct ExtraParameters {
	/// Pointer to the installer's live [`ExecFlags`].
	pub exec_flags: *mut ExecFlags,
	/// Runs a code segment in the installer. Honest passthrough, no wrapper.
	pub execute_code_segment: Option<unsafe extern "system" fn(c_int, Hwnd) -> c_int>,
	/// Replaces invalid filename characters in place.
	pub validate_filename: Option<unsafe extern "system" fn(*mut Tchar)>,
	/// Registers an unload callback. Returns 0 on success, 1 if already
	/// registered, and < 0 on error.
	pub register_plugin_callback:
		Option<unsafe extern "system" fn(Hmodule, NsisPluginCallback) -> c_int>,
}

/// A node of the installer's `GlobalAlloc`'d singly linked stack.
///
/// `text` is declared as a one-element array in C but is allocated with room
/// for `string_size` characters. Never construct one by value.
#[repr(C)]
pub struct StackNode {
	/// Next node, or null at the bottom of the stack.
	pub next: *mut StackNode,
	/// Start of the NUL-terminated string. Really `[Tchar; string_size]`.
	pub text: [Tchar; 1],
}

impl StackNode {
	/// Byte size of a node able to hold `string_size` characters, matching
	/// `GlobalAlloc(GPTR, sizeof(stack_t) + g_stringsize * sizeof(TCHAR))`.
	#[inline]
	pub const fn alloc_size(string_size: usize) -> usize {
		core::mem::size_of::<StackNode>() + string_size * core::mem::size_of::<Tchar>()
	}
}