From f1699aedc7e99cf3caedd9d67f48afa93e532275 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Tue, 23 Apr 2024 19:21:36 +0200 Subject: [PATCH 1/8] feat: use `no_std` for some plugins --- .cargo/config.toml | 3 - .changes/config.json | 10 -- Cargo.toml | 13 +- crates/nsis-download/Cargo.toml | 2 +- crates/nsis-download/src/lib.rs | 22 ++-- crates/nsis-fn/Cargo.toml | 13 ++ crates/nsis-fn/src/lib.rs | 40 ++++++ crates/nsis-plugin-api/Cargo.toml | 10 ++ crates/nsis-plugin-api/src/lib.rs | 182 +++++++++++++++++++++++++++ crates/nsis-process/Cargo.toml | 7 +- crates/nsis-process/build.rs | 3 + crates/nsis-process/src/lib.rs | 93 ++++++-------- crates/nsis-semvercompare/Cargo.toml | 9 +- crates/nsis-semvercompare/build.rs | 3 + crates/nsis-semvercompare/src/lib.rs | 33 +++-- crates/nsis-tauri-utils/Cargo.toml | 13 +- crates/nsis-tauri-utils/build.rs | 35 ++++++ crates/nsis-tauri-utils/src/lib.rs | 11 +- crates/pluginapi/Cargo.toml | 8 -- crates/pluginapi/src/lib.rs | 93 -------------- demo.nsi | 7 +- 21 files changed, 381 insertions(+), 229 deletions(-) create mode 100644 crates/nsis-fn/Cargo.toml create mode 100644 crates/nsis-fn/src/lib.rs create mode 100644 crates/nsis-plugin-api/Cargo.toml create mode 100644 crates/nsis-plugin-api/src/lib.rs create mode 100644 crates/nsis-process/build.rs create mode 100644 crates/nsis-semvercompare/build.rs create mode 100644 crates/nsis-tauri-utils/build.rs delete mode 100644 crates/pluginapi/Cargo.toml delete mode 100644 crates/pluginapi/src/lib.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 4acb8e5..9380186 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,2 @@ [build] target = "i686-pc-windows-msvc" - -[target.i686-pc-windows-msvc] -rustflags = ["-C", "target-feature=+crt-static"] diff --git a/.changes/config.json b/.changes/config.json index 72cfc9f..de57d3a 100644 --- a/.changes/config.json +++ b/.changes/config.json @@ -44,16 +44,6 @@ } ] }, - "nsis_download": { - "path": "./crates/nsis-download", - "manager": "rust", - "assets": [ - { - "path": "target/i686-pc-windows-msvc/release/${ pkg.pkg }.dll", - "name": "${ pkg.pkg }.dll" - } - ] - }, "nsis_process": { "path": "./crates/nsis-process", "manager": "rust", diff --git a/Cargo.toml b/Cargo.toml index a299928..8ede47b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,8 @@ edition = "2021" license = "MIT or Apache-2.0" [workspace.dependencies] -pluginapi = { path = "./crates/pluginapi" } +static_vcruntime = "2.0" +nsis-plugin-api = { path = "./crates/nsis-plugin-api" } [workspace.dependencies.windows-sys] version = "0.52.0" @@ -24,8 +25,8 @@ features = [ ] [profile.release] -codegen-units = 1 -lto = true -opt-level = "s" -panic = "abort" -strip = "symbols" +panic = "abort" # Strip expensive panic clean-up logic +codegen-units = 1 # Compile crates one after another so the compiler can optimize better +lto = true # Enables link to optimizations +opt-level = "s" # Optimize for binary size +strip = true # Remove debug symbols diff --git a/crates/nsis-download/Cargo.toml b/crates/nsis-download/Cargo.toml index e26069e..9240a72 100644 --- a/crates/nsis-download/Cargo.toml +++ b/crates/nsis-download/Cargo.toml @@ -11,5 +11,5 @@ crate-type = ["rlib", "cdylib"] [dependencies] ureq = { version = "2", default-features = false, features = ["tls"] } progress-streams = "1.1" -pluginapi = { workspace = true } +nsis-plugin-api = { workspace = true } windows-sys = { workspace = true } diff --git a/crates/nsis-download/src/lib.rs b/crates/nsis-download/src/lib.rs index a4103a1..0f502c3 100644 --- a/crates/nsis-download/src/lib.rs +++ b/crates/nsis-download/src/lib.rs @@ -1,6 +1,6 @@ use std::{fs, io, path::Path}; -use pluginapi::{exdll_init, popstring, pushint, stack_t, wchar_t}; +use nsis_plugin_api::*; use progress_streams::ProgressReader; use windows_sys::Win32::{ Foundation::HWND, @@ -19,20 +19,15 @@ use windows_sys::Win32::{ /// # Safety /// /// This function always expects 2 strings on the stack ($1: url, $2: path) and will panic otherwise. -#[no_mangle] -pub unsafe extern "C" fn Download( - hwnd_parent: HWND, - string_size: u32, - variables: *mut wchar_t, - stacktop: *mut *mut stack_t, -) { +#[nsis_fn] +fn Download() { exdll_init(string_size, variables, stacktop); let url = popstring().unwrap(); let path = popstring().unwrap(); let status = download_file(hwnd_parent, &url, &path); - pushint(status); + pushint(status).unwrap(); } fn download_file(hwnd_parent: HWND, url: &str, path: &str) -> i32 { @@ -139,15 +134,16 @@ fn download_file(hwnd_parent: HWND, url: &str, path: &str) -> i32 { let percentage = (read as f64 / total as f64) * 100.0; unsafe { SendMessageW(progress_bar, PBM_SETPOS, percentage as _, 0) }; - let text = pluginapi::encode_wide(format!( + let text = encode_wide(&format!( "{} / {} KiB - {:.2}%", read / 1024, total / 1024, percentage, - )); + )) + .unwrap(); unsafe { SetWindowTextW(progress_text, text.as_ptr()) }; - let text = pluginapi::encode_wide(format!("Downloading {} ...", url)); + let text = encode_wide(&format!("Downloading {} ...", url)).unwrap(); unsafe { SetWindowTextW(downloading_text, text.as_ptr()) }; if percentage >= 100. && !details_section_resized_back { @@ -177,7 +173,7 @@ fn download_file(hwnd_parent: HWND, url: &str, path: &str) -> i32 { } fn find_window(parent: HWND, class: impl AsRef) -> HWND { - let class = pluginapi::encode_wide(class.as_ref()); + let class = encode_wide(class.as_ref()).unwrap(); unsafe { FindWindowExW(parent, 0, class.as_ptr(), std::ptr::null()) } } diff --git a/crates/nsis-fn/Cargo.toml b/crates/nsis-fn/Cargo.toml new file mode 100644 index 0000000..bb7727f --- /dev/null +++ b/crates/nsis-fn/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nsis-fn" +version = "0.0.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = { version = "2", features = ["full"] } diff --git a/crates/nsis-fn/src/lib.rs b/crates/nsis-fn/src/lib.rs new file mode 100644 index 0000000..63f55e3 --- /dev/null +++ b/crates/nsis-fn/src/lib.rs @@ -0,0 +1,40 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse::Parse, parse_macro_input, ItemFn}; + +struct NsisFn { + func: ItemFn, +} + +impl Parse for NsisFn { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let func: ItemFn = input.parse()?; + Ok(Self { func }) + } +} + +#[proc_macro_attribute] +pub fn nsis_fn(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + let tokens = parse_macro_input!(tokens as NsisFn); + let NsisFn { func } = tokens; + + let ident = func.sig.ident; + let block = func.block; + let attrs = func.attrs; + + quote! { + #(#attrs)* + #[no_mangle] + #[allow(non_standard_style)] + pub unsafe extern "C" fn #ident( + hwnd_parent: ::windows_sys::Win32::Foundation::HWND, + string_size: core::ffi::c_int, + variables: *mut ::nsis_plugin_api::wchar_t, + stacktop: *mut *mut ::nsis_plugin_api::stack_t, + ) { + ::nsis_plugin_api::exdll_init(string_size, variables, stacktop); + #block + } + } + .into() +} diff --git a/crates/nsis-plugin-api/Cargo.toml b/crates/nsis-plugin-api/Cargo.toml new file mode 100644 index 0000000..4d619c7 --- /dev/null +++ b/crates/nsis-plugin-api/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "nsis-plugin-api" +version = "0.0.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +widestring = { version = "1.0", default-features = false, features = ["alloc"] } +windows-sys = { workspace = true } +nsis-fn = { path = "../nsis-fn" } diff --git a/crates/nsis-plugin-api/src/lib.rs b/crates/nsis-plugin-api/src/lib.rs new file mode 100644 index 0000000..d72c289 --- /dev/null +++ b/crates/nsis-plugin-api/src/lib.rs @@ -0,0 +1,182 @@ +#![no_std] +#![allow(unused)] +#![allow(nonstandard_style)] + +extern crate alloc; + +use core::{ + ffi::{c_int, c_void}, + fmt::Display, + mem::{size_of, size_of_val}, +}; + +use alloc::alloc::{GlobalAlloc, Layout}; +use alloc::string::{String, ToString}; +use alloc::vec; + +use widestring::U16CString; +use windows_sys::Win32::{ + Foundation::GlobalFree, + Globalization::{lstrcpyW, lstrcpynW}, + System::Memory::{ + GetProcessHeap, GlobalAlloc, HeapAlloc, HeapFree, HeapReAlloc, GPTR, HEAP_ZERO_MEMORY, + }, +}; + +pub use nsis_fn::nsis_fn; + +pub type wchar_t = i32; + +#[derive(Debug)] +pub enum Error { + StackIsNull, + ParseIntError(core::num::ParseIntError), + StrContainsNul(widestring::error::ContainsNul), +} + +impl Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Error::StackIsNull => write!(f, "Stack is null"), + Error::ParseIntError(e) => write!(f, "{}", e.to_string()), + Error::StrContainsNul(e) => write!(f, "{}", e.to_string()), + } + } +} + +impl From for Error { + fn from(value: core::num::ParseIntError) -> Self { + Self::ParseIntError(value) + } +} + +impl From> for Error { + fn from(value: widestring::error::ContainsNul) -> Self { + Self::StrContainsNul(value) + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct stack_t { + pub next: *mut stack_t, + pub text: [wchar_t; 1], +} + +static mut G_STRINGSIZE: c_int = 0; +static mut G_VARIABLES: *mut wchar_t = core::ptr::null_mut(); +static mut G_STACKTOP: *mut *mut stack_t = core::ptr::null_mut(); + +pub unsafe fn exdll_init(string_size: c_int, variables: *mut wchar_t, stacktop: *mut *mut stack_t) { + G_STRINGSIZE = string_size; + G_VARIABLES = variables; + G_STACKTOP = stacktop; +} + +pub unsafe fn pushstring(string: &str) -> Result<(), Error> { + if G_STACKTOP.is_null() { + return Err(Error::StackIsNull); + } + + let string = U16CString::from_str(string)?; + + let n = size_of::() + G_STRINGSIZE as usize * size_of_val(&string); + let th = GlobalAlloc(GPTR, n) as *mut stack_t; + lstrcpynW((*th).text.as_ptr() as _, string.as_ptr(), G_STRINGSIZE as _); + + (*th).next = *G_STACKTOP; + *G_STACKTOP = th; + + Ok(()) +} + +pub unsafe fn popstring() -> Result { + if G_STACKTOP.is_null() || (*G_STACKTOP).is_null() { + return Err(Error::StackIsNull); + } + + let th: *mut stack_t = *G_STACKTOP; + + let mut out = vec![0_u16; G_STRINGSIZE as _]; + lstrcpyW(out.as_mut_ptr(), (*th).text.as_ptr() as _); + + *G_STACKTOP = (*th).next; + + GlobalFree(th as _); + + decode_wide(&out) +} + +pub unsafe fn popint() -> Result { + popstring().and_then(|i| i.parse().map_err(Into::into)) +} + +pub unsafe fn pushint(int: i32) -> Result<(), Error> { + pushstring(&int.to_string()) +} + +pub fn encode_wide(string: &str) -> Result { + U16CString::from_str(string).map_err(Into::into) +} + +pub fn decode_wide(mut wide_c_string: &[u16]) -> Result { + if let Some(null_pos) = wide_c_string.iter().position(|c| *c == 0) { + wide_c_string = &wide_c_string[..null_pos]; + } + + U16CString::from_vec(wide_c_string) + .map(|s| s.to_string_lossy()) + .map_err(Into::into) +} + +#[global_allocator] +static WIN32_ALLOCATOR: Heapalloc = Heapalloc; + +pub struct Heapalloc; + +unsafe impl GlobalAlloc for Heapalloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + HeapAlloc(GetProcessHeap(), 0, layout.size()) as *mut u8 + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, layout.size()) as *mut u8 + } + + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + HeapFree(GetProcessHeap(), 0, ptr as *mut c_void); + } + + unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 { + HeapReAlloc( + GetProcessHeap(), + HEAP_ZERO_MEMORY, + ptr as *mut c_void, + new_size, + ) as *mut u8 + } +} + +/// Sets up the needed functions for the NSIS plugin dll, +/// like `main`, `panic` and `__CxxFrameHandler3` extern functions +#[macro_export] +macro_rules! nsis_plugin { + () => { + #[no_mangle] + extern "C" fn main() -> i32 { + 0 + } + + #[cfg(not(test))] + #[panic_handler] + fn panic(_info: &core::panic::PanicInfo) -> ! { + unsafe { ::windows_sys::Win32::System::Threading::ExitProcess(u32::MAX) } + } + + // wrong signature but shouldn't matter + #[no_mangle] + extern "C" fn __CxxFrameHandler3() { + unsafe { ::windows_sys::Win32::System::Threading::ExitProcess(u32::MAX) }; + } + }; +} diff --git a/crates/nsis-process/Cargo.toml b/crates/nsis-process/Cargo.toml index 0867d69..5d0b77e 100644 --- a/crates/nsis-process/Cargo.toml +++ b/crates/nsis-process/Cargo.toml @@ -6,8 +6,11 @@ edition = { workspace = true } license = { workspace = true } [lib] -crate-type = [ "rlib", "cdylib" ] +crate-type = ["cdylib"] + +[build-dependencies] +static_vcruntime = { workspace = true } [dependencies] -pluginapi = { workspace = true } +nsis-plugin-api = { workspace = true } windows-sys = { workspace = true } diff --git a/crates/nsis-process/build.rs b/crates/nsis-process/build.rs new file mode 100644 index 0000000..20e1c8e --- /dev/null +++ b/crates/nsis-process/build.rs @@ -0,0 +1,3 @@ +fn main() { + static_vcruntime::metabuild(); +} diff --git a/crates/nsis-process/src/lib.rs b/crates/nsis-process/src/lib.rs index e855f8d..0d3d12e 100644 --- a/crates/nsis-process/src/lib.rs +++ b/crates/nsis-process/src/lib.rs @@ -1,9 +1,15 @@ -use std::{ffi::c_void, mem, ptr}; +#![no_std] +#![no_main] -use pluginapi::{decode_wide, exdll_init, popstring, pushint, stack_t, wchar_t}; +extern crate alloc; +use alloc::vec; +use alloc::vec::Vec; +use core::{ffi::c_void, mem, ptr}; + +use nsis_plugin_api::*; use windows_sys::Win32::{ - Foundation::{CloseHandle, HANDLE, HWND}, + Foundation::{CloseHandle, HANDLE}, Security::{EqualSid, GetTokenInformation, TokenUser, TOKEN_QUERY, TOKEN_USER}, System::{ Diagnostics::ToolHelp::{ @@ -11,32 +17,27 @@ use windows_sys::Win32::{ TH32CS_SNAPPROCESS, }, Threading::{ - OpenProcess, OpenProcessToken, TerminateProcess, PROCESS_QUERY_INFORMATION, - PROCESS_TERMINATE, + GetCurrentProcessId, OpenProcess, OpenProcessToken, TerminateProcess, + PROCESS_QUERY_INFORMATION, PROCESS_TERMINATE, }, }, }; +nsis_plugin!(); + /// Test if there is a running process with the given name, skipping processes with the host's pid. The input and process names are case-insensitive. /// /// # Safety /// /// This function always expects 1 string on the stack ($1: name) and will panic otherwise. -#[no_mangle] -pub unsafe extern "C" fn FindProcess( - _hwnd_parent: HWND, - string_size: u32, - variables: *mut wchar_t, - stacktop: *mut *mut stack_t, -) { - exdll_init(string_size, variables, stacktop); - +#[nsis_fn] +fn FindProcess() { let name = popstring().unwrap(); if !get_processes(&name).is_empty() { - pushint(0); + pushint(0).unwrap(); } else { - pushint(1); + pushint(1).unwrap(); } } @@ -45,33 +46,26 @@ pub unsafe extern "C" fn FindProcess( /// # Safety /// /// This function always expects 1 string on the stack ($1: name) and will panic otherwise. -#[no_mangle] -pub unsafe extern "C" fn FindProcessCurrentUser( - _hwnd_parent: HWND, - string_size: u32, - variables: *mut wchar_t, - stacktop: *mut *mut stack_t, -) { - exdll_init(string_size, variables, stacktop); - +#[nsis_fn] +fn FindProcessCurrentUser() { let name = popstring().unwrap(); let processes = get_processes(&name); - if let Some(user_sid) = get_sid(std::process::id()) { + if let Some(user_sid) = get_sid(GetCurrentProcessId()) { if processes .into_iter() .any(|pid| belongs_to_user(user_sid, pid)) { - pushint(0); + pushint(0).unwrap(); } else { - pushint(1); + pushint(1).unwrap(); } // Fall back to perMachine checks if we can't get current user id } else if processes.is_empty() { - pushint(1); + pushint(1).unwrap(); } else { - pushint(0); + pushint(0).unwrap(); } } @@ -80,23 +74,16 @@ pub unsafe extern "C" fn FindProcessCurrentUser( /// # Safety /// /// This function always expects 1 string on the stack ($1: name) and will panic otherwise. -#[no_mangle] -pub unsafe extern "C" fn KillProcess( - _hwnd_parent: HWND, - string_size: u32, - variables: *mut wchar_t, - stacktop: *mut *mut stack_t, -) { - exdll_init(string_size, variables, stacktop); - +#[nsis_fn] +fn KillProcess() { let name = popstring().unwrap(); let processes = get_processes(&name); if !processes.is_empty() && processes.into_iter().map(kill).all(|b| b) { - pushint(0); + pushint(0).unwrap(); } else { - pushint(1); + pushint(1).unwrap(); } } @@ -105,25 +92,18 @@ pub unsafe extern "C" fn KillProcess( /// # Safety /// /// This function always expects 1 string on the stack ($1: name) and will panic otherwise. -#[no_mangle] -pub unsafe extern "C" fn KillProcessCurrentUser( - _hwnd_parent: HWND, - string_size: u32, - variables: *mut wchar_t, - stacktop: *mut *mut stack_t, -) { - exdll_init(string_size, variables, stacktop); - +#[nsis_fn] +fn KillProcessCurrentUser() { let name = popstring().unwrap(); let processes = get_processes(&name); if processes.is_empty() { - pushint(1); + pushint(1).unwrap(); return; } - let success = if let Some(user_sid) = get_sid(std::process::id()) { + let success = if let Some(user_sid) = get_sid(GetCurrentProcessId()) { processes .into_iter() .filter(|pid| belongs_to_user(user_sid, *pid)) @@ -134,9 +114,9 @@ pub unsafe extern "C" fn KillProcessCurrentUser( }; if success { - pushint(0) + pushint(0).unwrap() } else { - pushint(1) + pushint(1).unwrap() } } @@ -204,7 +184,7 @@ unsafe fn get_sid(pid: u32) -> Option<*mut c_void> { } fn get_processes(name: &str) -> Vec { - let current_pid = std::process::id(); + let current_pid = unsafe { GetCurrentProcessId() }; let mut processes = Vec::new(); unsafe { @@ -219,7 +199,6 @@ fn get_processes(name: &str) -> Vec { while Process32NextW(handle, &mut process) != 0 { if current_pid != process.th32ProcessID && decode_wide(&process.szExeFile) - .to_str() .unwrap_or_default() .to_lowercase() == name.to_lowercase() @@ -242,14 +221,12 @@ mod tests { #[test] fn find_process() { let processes = get_processes("explorer.exe"); - dbg!(&processes); assert!(!processes.is_empty()); } #[test] fn kill_process() { let processes = get_processes("something_that_doesnt_exist.exe"); - dbg!(&processes); // TODO: maybe find some way to spawn a dummy process we can kill here? // This will return true on empty iterators so it's basically no-op right now assert!(processes.into_iter().map(kill).all(|b| b)); diff --git a/crates/nsis-semvercompare/Cargo.toml b/crates/nsis-semvercompare/Cargo.toml index 0b8ccb2..bbe7bb8 100644 --- a/crates/nsis-semvercompare/Cargo.toml +++ b/crates/nsis-semvercompare/Cargo.toml @@ -6,9 +6,12 @@ edition = { workspace = true } license = { workspace = true } [lib] -crate-type = ["rlib", "cdylib"] +crate-type = ["cdylib"] + +[build-dependencies] +static_vcruntime = { workspace = true } [dependencies] -semver = "1.0" -pluginapi = { workspace = true } +semver = { version = "1.0", default-features = false } +nsis-plugin-api = { workspace = true } windows-sys = { workspace = true } diff --git a/crates/nsis-semvercompare/build.rs b/crates/nsis-semvercompare/build.rs new file mode 100644 index 0000000..20e1c8e --- /dev/null +++ b/crates/nsis-semvercompare/build.rs @@ -0,0 +1,3 @@ +fn main() { + static_vcruntime::metabuild(); +} diff --git a/crates/nsis-semvercompare/src/lib.rs b/crates/nsis-semvercompare/src/lib.rs index c8b49d4..0afe32d 100644 --- a/crates/nsis-semvercompare/src/lib.rs +++ b/crates/nsis-semvercompare/src/lib.rs @@ -1,33 +1,30 @@ -use std::{cmp::Ordering, str::FromStr}; +#![no_std] +#![no_main] -use pluginapi::{exdll_init, popstring, pushint, stack_t, wchar_t}; +use core::cmp::Ordering; + +use nsis_plugin_api::*; use semver::Version; -use windows_sys::Win32::Foundation::HWND; + +nsis_plugin!(); /// Compare two semantic versions. /// /// # Safety /// /// This function always expects 2 strings on the stack ($1: version1, $2: version2) and will panic otherwise. -#[no_mangle] -pub unsafe extern "C" fn SemverCompare( - _hwnd_parent: HWND, - string_size: u32, - variables: *mut wchar_t, - stacktop: *mut *mut stack_t, -) { - exdll_init(string_size, variables, stacktop); - +#[nsis_fn] +fn SemverCompare() { let v1 = popstring().unwrap(); let v2 = popstring().unwrap(); - let ret = semver_compare(&v1, &v2); - pushint(ret); + let ret = compare(&v1, &v2); + pushint(ret).unwrap() } -fn semver_compare(v1: &str, v2: &str) -> i32 { - let v1 = Version::from_str(v1); - let v2 = Version::from_str(v2); +fn compare(v1: &str, v2: &str) -> i32 { + let v1 = Version::parse(v1); + let v2 = Version::parse(v2); let (v1, v2) = match (v1, v2) { (Ok(_), Err(_)) => return 1, @@ -64,7 +61,7 @@ mod tests { ("1.2.1-fffasd.1", "1.2.1-dasdqwe.1", 1), ("1.2.1-gasfdlkj.1", "1.2.1-calskjd.1", 1), ] { - assert_eq!(semver_compare(v1, v2), ret); + assert_eq!(compare(v1, v2), ret); } } } diff --git a/crates/nsis-tauri-utils/Cargo.toml b/crates/nsis-tauri-utils/Cargo.toml index 393dd67..b99decf 100644 --- a/crates/nsis-tauri-utils/Cargo.toml +++ b/crates/nsis-tauri-utils/Cargo.toml @@ -1,14 +1,17 @@ [package] name = "nsis-tauri-utils" -version = "0.2.2" +version = "0.2.0" authors = { workspace = true } edition = { workspace = true } license = { workspace = true } [lib] -crate-type = [ "cdylib" ] +crate-type = ["cdylib"] + +[build-dependencies] +static_vcruntime = { workspace = true } [dependencies] -nsis-download = { path = "../nsis-download" } -nsis-process = { path = "../nsis-process" } -nsis-semvercompare = { path = "../nsis-semvercompare" } +nsis-plugin-api = { workspace = true } +windows-sys = { workspace = true } +semver = { version = "1.0", default-features = false } diff --git a/crates/nsis-tauri-utils/build.rs b/crates/nsis-tauri-utils/build.rs new file mode 100644 index 0000000..ad5a5ff --- /dev/null +++ b/crates/nsis-tauri-utils/build.rs @@ -0,0 +1,35 @@ +use std::{fs::File, io::Write}; + +fn main() { + write_plugins(); + static_vcruntime::metabuild(); +} + +fn write_plugins() { + let out_dir = std::env::var("OUT_DIR").unwrap(); + let path = format!("{out_dir}/combined_libs.rs"); + + let mut file = File::options() + .truncate(true) + .write(true) + .create(true) + .open(&path) + .unwrap(); + + let p1 = include_str!("../nsis-semvercompare/src/lib.rs"); + let p2 = include_str!("../nsis-process/src/lib.rs"); + for p in [p1, p2] { + let lines = p + .lines() + .filter(|l| { + !(l.contains("#![no_std]") + || l.contains("#![no_main]") + || l.contains("use nsis_plugin_api::*;") + || l.contains("nsis_plugin!();")) + }) + .collect::>(); + + let content = lines.join("\n"); + file.write_all(content.as_bytes()).unwrap(); + } +} diff --git a/crates/nsis-tauri-utils/src/lib.rs b/crates/nsis-tauri-utils/src/lib.rs index 95d4ac3..821480c 100644 --- a/crates/nsis-tauri-utils/src/lib.rs +++ b/crates/nsis-tauri-utils/src/lib.rs @@ -1,3 +1,8 @@ -pub use nsis_download::*; -pub use nsis_process::*; -pub use nsis_semvercompare::*; +#![no_std] +#![no_main] + +use nsis_plugin_api::*; + +nsis_plugin!(); + +include!(concat!(env!("OUT_DIR"), "/combined_libs.rs")); diff --git a/crates/pluginapi/Cargo.toml b/crates/pluginapi/Cargo.toml deleted file mode 100644 index 0e006cf..0000000 --- a/crates/pluginapi/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "pluginapi" -version = "0.0.0" -edition = "2021" -license = "MIT OR Apache-2.0" - -[dependencies] -windows-sys = { workspace = true } diff --git a/crates/pluginapi/src/lib.rs b/crates/pluginapi/src/lib.rs deleted file mode 100644 index 9cb27b9..0000000 --- a/crates/pluginapi/src/lib.rs +++ /dev/null @@ -1,93 +0,0 @@ -#![allow(clippy::missing_safety_doc)] -#![allow(non_camel_case_types)] - -use std::{ - ffi::{OsStr, OsString}, - iter::once, - mem::{size_of, size_of_val}, - os::windows::prelude::{OsStrExt, OsStringExt}, -}; - -use windows_sys::Win32::{ - Foundation::GlobalFree, - Globalization::{lstrcpyW, lstrcpynW}, - System::Memory::{GlobalAlloc, GPTR}, -}; - -static mut G_STRINGSIZE: u32 = 0; -static mut G_VARIABLES: *mut wchar_t = std::ptr::null_mut(); -static mut G_STACKTOP: *mut *mut stack_t = std::ptr::null_mut(); - -pub unsafe fn exdll_init(string_size: u32, variables: *mut wchar_t, stacktop: *mut *mut stack_t) { - G_STRINGSIZE = string_size; - G_VARIABLES = variables; - G_STACKTOP = stacktop; -} - -pub type wchar_t = i32; - -#[derive(Debug)] -pub enum Error { - InvalidStackError, - InvalidUnicode, -} - -#[repr(C)] -#[derive(Debug)] -pub struct stack_t { - next: *mut stack_t, - text: [wchar_t; 1], -} - -pub unsafe fn pushstring(s: impl AsRef) { - if G_STACKTOP.is_null() { - return; - } - - let string_wide = encode_wide(s); - let th: *mut stack_t = GlobalAlloc( - GPTR, - size_of::() + G_STRINGSIZE as usize * size_of_val(&string_wide), - ) as _; - lstrcpynW( - (*th).text.as_ptr() as _, - string_wide.as_ptr() as _, - G_STRINGSIZE as _, - ); - (*th).next = *G_STACKTOP; - *G_STACKTOP = th; -} - -pub unsafe fn popstring() -> Result { - if G_STACKTOP.is_null() || (*G_STACKTOP).is_null() { - return Err(Error::InvalidStackError); - } - - let mut string_wide: Vec = vec![0; G_STRINGSIZE as _]; - let th: *mut stack_t = *G_STACKTOP; - lstrcpyW(string_wide.as_mut_ptr(), (*th).text.as_ptr() as _); - let string = decode_wide(&string_wide) - .to_str() - .ok_or(Error::InvalidUnicode)? - .to_string(); - *G_STACKTOP = (*th).next; - GlobalFree(th as _); - - Ok(string) -} - -pub unsafe fn pushint(int: i32) { - pushstring(int.to_string()) -} - -pub fn encode_wide(string: impl AsRef) -> Vec { - string.as_ref().encode_wide().chain(once(0)).collect() -} - -pub fn decode_wide(mut wide_c_string: &[u16]) -> OsString { - if let Some(null_pos) = wide_c_string.iter().position(|c| *c == 0) { - wide_c_string = &wide_c_string[..null_pos]; - } - - OsString::from_wide(wide_c_string) -} diff --git a/demo.nsi b/demo.nsi index 52d164f..261671b 100644 --- a/demo.nsi +++ b/demo.nsi @@ -1,17 +1,12 @@ Name "demo" OutFile "demo.exe" Unicode true +ShowInstDetails show -!addplugindir ".\target\release" -!addplugindir ".\target\debug" !addplugindir ".\target\i686-pc-windows-msvc\release" !addplugindir ".\target\i686-pc-windows-msvc\debug" -!addplugindir "$%CARGO_TARGET_DIR%\release" -!addplugindir "$%CARGO_TARGET_DIR%\debug" !addplugindir "$%CARGO_TARGET_DIR%\i686-pc-windows-msvc\release" !addplugindir "$%CARGO_TARGET_DIR%\i686-pc-windows-msvc\debug" -!addplugindir "$%CARGO_BUILD_TARGET_DIR%\release" -!addplugindir "$%CARGO_BUILD_TARGET_DIR%\debug" !addplugindir "$%CARGO_BUILD_TARGET_DIR%\i686-pc-windows-msvc\release" !addplugindir "$%CARGO_BUILD_TARGET_DIR%\i686-pc-windows-msvc\debug" From f6a4218bdfcd42b98d59c9cfe162156c3b3bdf99 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Sat, 27 Apr 2024 17:02:00 +0300 Subject: [PATCH 2/8] no static vcruntime --- Cargo.toml | 2 +- crates/nsis-download/src/lib.rs | 13 ++- crates/nsis-plugin-api/Cargo.toml | 1 - crates/nsis-plugin-api/src/lib.rs | 146 ++++++++++++++------------- crates/nsis-process/Cargo.toml | 3 - crates/nsis-process/build.rs | 2 +- crates/nsis-process/src/lib.rs | 36 +++---- crates/nsis-semvercompare/Cargo.toml | 3 - crates/nsis-semvercompare/build.rs | 2 +- crates/nsis-semvercompare/src/lib.rs | 17 ++-- crates/nsis-tauri-utils/Cargo.toml | 3 - crates/nsis-tauri-utils/build.rs | 9 +- crates/nsis-tauri-utils/src/lib.rs | 1 - demo.nsi | 6 +- 14 files changed, 119 insertions(+), 125 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8ede47b..bf7a5ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = ["crates/*"] +exclude = ["crates/nsis-download"] resolver = "2" [workspace.package] @@ -8,7 +9,6 @@ edition = "2021" license = "MIT or Apache-2.0" [workspace.dependencies] -static_vcruntime = "2.0" nsis-plugin-api = { path = "./crates/nsis-plugin-api" } [workspace.dependencies.windows-sys] diff --git a/crates/nsis-download/src/lib.rs b/crates/nsis-download/src/lib.rs index 0f502c3..9051c0f 100644 --- a/crates/nsis-download/src/lib.rs +++ b/crates/nsis-download/src/lib.rs @@ -23,11 +23,11 @@ use windows_sys::Win32::{ fn Download() { exdll_init(string_size, variables, stacktop); - let url = popstring().unwrap(); - let path = popstring().unwrap(); + let url = popstring(); + let path = popstring(); let status = download_file(hwnd_parent, &url, &path); - pushint(status).unwrap(); + pushint(status); } fn download_file(hwnd_parent: HWND, url: &str, path: &str) -> i32 { @@ -139,11 +139,10 @@ fn download_file(hwnd_parent: HWND, url: &str, path: &str) -> i32 { read / 1024, total / 1024, percentage, - )) - .unwrap(); + )); unsafe { SetWindowTextW(progress_text, text.as_ptr()) }; - let text = encode_wide(&format!("Downloading {} ...", url)).unwrap(); + let text = encode_wide(&format!("Downloading {} ...", url)); unsafe { SetWindowTextW(downloading_text, text.as_ptr()) }; if percentage >= 100. && !details_section_resized_back { @@ -173,7 +172,7 @@ fn download_file(hwnd_parent: HWND, url: &str, path: &str) -> i32 { } fn find_window(parent: HWND, class: impl AsRef) -> HWND { - let class = encode_wide(class.as_ref()).unwrap(); + let class = encode_wide(class.as_ref()); unsafe { FindWindowExW(parent, 0, class.as_ptr(), std::ptr::null()) } } diff --git a/crates/nsis-plugin-api/Cargo.toml b/crates/nsis-plugin-api/Cargo.toml index 4d619c7..89a1cbb 100644 --- a/crates/nsis-plugin-api/Cargo.toml +++ b/crates/nsis-plugin-api/Cargo.toml @@ -5,6 +5,5 @@ edition = "2021" license = "MIT OR Apache-2.0" [dependencies] -widestring = { version = "1.0", default-features = false, features = ["alloc"] } windows-sys = { workspace = true } nsis-fn = { path = "../nsis-fn" } diff --git a/crates/nsis-plugin-api/src/lib.rs b/crates/nsis-plugin-api/src/lib.rs index d72c289..d56ea78 100644 --- a/crates/nsis-plugin-api/src/lib.rs +++ b/crates/nsis-plugin-api/src/lib.rs @@ -7,14 +7,17 @@ extern crate alloc; use core::{ ffi::{c_int, c_void}, fmt::Display, + iter, mem::{size_of, size_of_val}, }; -use alloc::alloc::{GlobalAlloc, Layout}; use alloc::string::{String, ToString}; use alloc::vec; +use alloc::{ + alloc::{GlobalAlloc, Layout}, + vec::Vec, +}; -use widestring::U16CString; use windows_sys::Win32::{ Foundation::GlobalFree, Globalization::{lstrcpyW, lstrcpynW}, @@ -27,35 +30,6 @@ pub use nsis_fn::nsis_fn; pub type wchar_t = i32; -#[derive(Debug)] -pub enum Error { - StackIsNull, - ParseIntError(core::num::ParseIntError), - StrContainsNul(widestring::error::ContainsNul), -} - -impl Display for Error { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Error::StackIsNull => write!(f, "Stack is null"), - Error::ParseIntError(e) => write!(f, "{}", e.to_string()), - Error::StrContainsNul(e) => write!(f, "{}", e.to_string()), - } - } -} - -impl From for Error { - fn from(value: core::num::ParseIntError) -> Self { - Self::ParseIntError(value) - } -} - -impl From> for Error { - fn from(value: widestring::error::ContainsNul) -> Self { - Self::StrContainsNul(value) - } -} - #[repr(C)] #[derive(Debug)] pub struct stack_t { @@ -73,60 +47,62 @@ pub unsafe fn exdll_init(string_size: c_int, variables: *mut wchar_t, stacktop: G_STACKTOP = stacktop; } -pub unsafe fn pushstring(string: &str) -> Result<(), Error> { +pub const ONE: [u16; 2] = [49, 0]; +pub const ZERO: [u16; 2] = [48, 0]; +pub const NEGATIVE_ONE: [u16; 3] = [45, 49, 0]; + +pub unsafe fn push(bytes: &[u16]) { if G_STACKTOP.is_null() { - return Err(Error::StackIsNull); + return; } - let string = U16CString::from_str(string)?; - - let n = size_of::() + G_STRINGSIZE as usize * size_of_val(&string); + let n = size_of::() + G_STRINGSIZE as usize * 2; let th = GlobalAlloc(GPTR, n) as *mut stack_t; - lstrcpynW((*th).text.as_ptr() as _, string.as_ptr(), G_STRINGSIZE as _); - + lstrcpyW((*th).text.as_ptr() as _, bytes.as_ptr()); (*th).next = *G_STACKTOP; *G_STACKTOP = th; +} + +pub unsafe fn pushstr(str: &str) { + let bytes = encode_wide(str); + push(&bytes) +} - Ok(()) +pub unsafe fn pushint(int: i32) { + let str = int.to_string(); + pushstr(&str) } -pub unsafe fn popstring() -> Result { +pub unsafe fn pop(out: &mut [u16]) { if G_STACKTOP.is_null() || (*G_STACKTOP).is_null() { - return Err(Error::StackIsNull); + return; } let th: *mut stack_t = *G_STACKTOP; - - let mut out = vec![0_u16; G_STRINGSIZE as _]; lstrcpyW(out.as_mut_ptr(), (*th).text.as_ptr() as _); - *G_STACKTOP = (*th).next; - GlobalFree(th as _); - - decode_wide(&out) } -pub unsafe fn popint() -> Result { - popstring().and_then(|i| i.parse().map_err(Into::into)) +pub unsafe fn popstring() -> String { + let mut bytes = vec![0_u16; G_STRINGSIZE as _]; + pop(&mut bytes); + decode_wide(&bytes) } -pub unsafe fn pushint(int: i32) -> Result<(), Error> { - pushstring(&int.to_string()) +pub fn encode_wide(str: &str) -> Vec { + str.encode_utf16() + .chain(iter::once(0)) + .collect::>() } -pub fn encode_wide(string: &str) -> Result { - U16CString::from_str(string).map_err(Into::into) -} - -pub fn decode_wide(mut wide_c_string: &[u16]) -> Result { - if let Some(null_pos) = wide_c_string.iter().position(|c| *c == 0) { - wide_c_string = &wide_c_string[..null_pos]; - } - - U16CString::from_vec(wide_c_string) - .map(|s| s.to_string_lossy()) - .map_err(Into::into) +pub fn decode_wide(bytes: &[u16]) -> String { + let bytes = bytes + .iter() + .position(|c| *c == 0) + .map(|nul| &bytes[..nul]) + .unwrap_or(&bytes); + String::from_utf16_lossy(bytes) } #[global_allocator] @@ -158,13 +134,17 @@ unsafe impl GlobalAlloc for Heapalloc { } /// Sets up the needed functions for the NSIS plugin dll, -/// like `main`, `panic` and `__CxxFrameHandler3` extern functions +/// like `main`, `panic` and `mem*` extern functions #[macro_export] macro_rules! nsis_plugin { () => { #[no_mangle] - extern "C" fn main() -> i32 { - 0 + extern "system" fn DllMain( + dll_module: ::windows_sys::Win32::Foundation::HINSTANCE, + call_reason: u32, + _: *mut (), + ) -> bool { + true } #[cfg(not(test))] @@ -173,10 +153,38 @@ macro_rules! nsis_plugin { unsafe { ::windows_sys::Win32::System::Threading::ExitProcess(u32::MAX) } } - // wrong signature but shouldn't matter #[no_mangle] - extern "C" fn __CxxFrameHandler3() { - unsafe { ::windows_sys::Win32::System::Threading::ExitProcess(u32::MAX) }; + pub unsafe extern "C" fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 { + let mut i = 0; + while i < n { + *dest.offset(i as isize) = *src.offset(i as isize); + i += 1; + } + return dest; + } + + #[no_mangle] + pub unsafe extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 { + let mut i = 0; + while i < n { + let a = *s1.offset(i as isize); + let b = *s2.offset(i as isize); + if a != b { + return a as i32 - b as i32; + } + i += 1; + } + return 0; + } + + #[no_mangle] + pub unsafe extern "C" fn memset(s: *mut u8, c: i32, n: usize) -> *mut u8 { + let mut i = 0; + while i < n { + *s.offset(i as isize) = c as u8; + i += 1; + } + return s; } }; } diff --git a/crates/nsis-process/Cargo.toml b/crates/nsis-process/Cargo.toml index 5d0b77e..61ed417 100644 --- a/crates/nsis-process/Cargo.toml +++ b/crates/nsis-process/Cargo.toml @@ -8,9 +8,6 @@ license = { workspace = true } [lib] crate-type = ["cdylib"] -[build-dependencies] -static_vcruntime = { workspace = true } - [dependencies] nsis-plugin-api = { workspace = true } windows-sys = { workspace = true } diff --git a/crates/nsis-process/build.rs b/crates/nsis-process/build.rs index 20e1c8e..f27bc53 100644 --- a/crates/nsis-process/build.rs +++ b/crates/nsis-process/build.rs @@ -1,3 +1,3 @@ fn main() { - static_vcruntime::metabuild(); + println!("cargo::rustc-link-arg=/ENTRY:DllMain") } diff --git a/crates/nsis-process/src/lib.rs b/crates/nsis-process/src/lib.rs index 0d3d12e..bbedb79 100644 --- a/crates/nsis-process/src/lib.rs +++ b/crates/nsis-process/src/lib.rs @@ -1,5 +1,4 @@ #![no_std] -#![no_main] extern crate alloc; @@ -32,12 +31,12 @@ nsis_plugin!(); /// This function always expects 1 string on the stack ($1: name) and will panic otherwise. #[nsis_fn] fn FindProcess() { - let name = popstring().unwrap(); + let name = popstring(); if !get_processes(&name).is_empty() { - pushint(0).unwrap(); + push(&ZERO); } else { - pushint(1).unwrap(); + push(&ONE); } } @@ -48,7 +47,7 @@ fn FindProcess() { /// This function always expects 1 string on the stack ($1: name) and will panic otherwise. #[nsis_fn] fn FindProcessCurrentUser() { - let name = popstring().unwrap(); + let name = popstring(); let processes = get_processes(&name); @@ -57,15 +56,15 @@ fn FindProcessCurrentUser() { .into_iter() .any(|pid| belongs_to_user(user_sid, pid)) { - pushint(0).unwrap(); + push(&ZERO); } else { - pushint(1).unwrap(); + push(&ONE); } // Fall back to perMachine checks if we can't get current user id } else if processes.is_empty() { - pushint(1).unwrap(); + push(&ONE); } else { - pushint(0).unwrap(); + push(&ZERO); } } @@ -76,14 +75,14 @@ fn FindProcessCurrentUser() { /// This function always expects 1 string on the stack ($1: name) and will panic otherwise. #[nsis_fn] fn KillProcess() { - let name = popstring().unwrap(); + let name = popstring(); let processes = get_processes(&name); if !processes.is_empty() && processes.into_iter().map(kill).all(|b| b) { - pushint(0).unwrap(); + push(&ZERO); } else { - pushint(1).unwrap(); + push(&ONE); } } @@ -94,12 +93,12 @@ fn KillProcess() { /// This function always expects 1 string on the stack ($1: name) and will panic otherwise. #[nsis_fn] fn KillProcessCurrentUser() { - let name = popstring().unwrap(); + let name = popstring(); let processes = get_processes(&name); if processes.is_empty() { - pushint(1).unwrap(); + push(&ONE); return; } @@ -114,9 +113,9 @@ fn KillProcessCurrentUser() { }; if success { - pushint(0).unwrap() + push(&ZERO) } else { - pushint(1).unwrap() + push(&ONE) } } @@ -198,10 +197,7 @@ fn get_processes(name: &str) -> Vec { if Process32FirstW(handle, &mut process) != 0 { while Process32NextW(handle, &mut process) != 0 { if current_pid != process.th32ProcessID - && decode_wide(&process.szExeFile) - .unwrap_or_default() - .to_lowercase() - == name.to_lowercase() + && decode_wide(&process.szExeFile).to_lowercase() == name.to_lowercase() { processes.push(process.th32ProcessID); } diff --git a/crates/nsis-semvercompare/Cargo.toml b/crates/nsis-semvercompare/Cargo.toml index bbe7bb8..e1a5469 100644 --- a/crates/nsis-semvercompare/Cargo.toml +++ b/crates/nsis-semvercompare/Cargo.toml @@ -8,9 +8,6 @@ license = { workspace = true } [lib] crate-type = ["cdylib"] -[build-dependencies] -static_vcruntime = { workspace = true } - [dependencies] semver = { version = "1.0", default-features = false } nsis-plugin-api = { workspace = true } diff --git a/crates/nsis-semvercompare/build.rs b/crates/nsis-semvercompare/build.rs index 20e1c8e..f27bc53 100644 --- a/crates/nsis-semvercompare/build.rs +++ b/crates/nsis-semvercompare/build.rs @@ -1,3 +1,3 @@ fn main() { - static_vcruntime::metabuild(); + println!("cargo::rustc-link-arg=/ENTRY:DllMain") } diff --git a/crates/nsis-semvercompare/src/lib.rs b/crates/nsis-semvercompare/src/lib.rs index 0afe32d..171d69b 100644 --- a/crates/nsis-semvercompare/src/lib.rs +++ b/crates/nsis-semvercompare/src/lib.rs @@ -1,5 +1,4 @@ #![no_std] -#![no_main] use core::cmp::Ordering; @@ -10,16 +9,22 @@ nsis_plugin!(); /// Compare two semantic versions. /// +/// Returns `0` if equal, `1` if `$v1` is newer and `-1` if `$v2` is newer. +/// /// # Safety /// -/// This function always expects 2 strings on the stack ($1: version1, $2: version2) and will panic otherwise. +/// This function always expects 2 strings on the stack ($v1, $v2) and will panic otherwise. #[nsis_fn] fn SemverCompare() { - let v1 = popstring().unwrap(); - let v2 = popstring().unwrap(); + let v1 = popstring(); + let v2 = popstring(); - let ret = compare(&v1, &v2); - pushint(ret).unwrap() + match compare(&v1, &v2) { + -1 => push(&NEGATIVE_ONE), + 0 => push(&ZERO), + 1 => push(&ONE), + _ => unreachable!(), + } } fn compare(v1: &str, v2: &str) -> i32 { diff --git a/crates/nsis-tauri-utils/Cargo.toml b/crates/nsis-tauri-utils/Cargo.toml index b99decf..56d3c73 100644 --- a/crates/nsis-tauri-utils/Cargo.toml +++ b/crates/nsis-tauri-utils/Cargo.toml @@ -8,9 +8,6 @@ license = { workspace = true } [lib] crate-type = ["cdylib"] -[build-dependencies] -static_vcruntime = { workspace = true } - [dependencies] nsis-plugin-api = { workspace = true } windows-sys = { workspace = true } diff --git a/crates/nsis-tauri-utils/build.rs b/crates/nsis-tauri-utils/build.rs index ad5a5ff..cb35723 100644 --- a/crates/nsis-tauri-utils/build.rs +++ b/crates/nsis-tauri-utils/build.rs @@ -1,15 +1,13 @@ -use std::{fs::File, io::Write}; - fn main() { write_plugins(); - static_vcruntime::metabuild(); + println!("cargo::rustc-link-arg=/ENTRY:DllMain") } fn write_plugins() { let out_dir = std::env::var("OUT_DIR").unwrap(); let path = format!("{out_dir}/combined_libs.rs"); - let mut file = File::options() + let mut file = std::fs::File::options() .truncate(true) .write(true) .create(true) @@ -23,13 +21,12 @@ fn write_plugins() { .lines() .filter(|l| { !(l.contains("#![no_std]") - || l.contains("#![no_main]") || l.contains("use nsis_plugin_api::*;") || l.contains("nsis_plugin!();")) }) .collect::>(); let content = lines.join("\n"); - file.write_all(content.as_bytes()).unwrap(); + std::io::Write::write_all(&mut file, content.as_bytes()).unwrap(); } } diff --git a/crates/nsis-tauri-utils/src/lib.rs b/crates/nsis-tauri-utils/src/lib.rs index 821480c..155bf34 100644 --- a/crates/nsis-tauri-utils/src/lib.rs +++ b/crates/nsis-tauri-utils/src/lib.rs @@ -1,5 +1,4 @@ #![no_std] -#![no_main] use nsis_plugin_api::*; diff --git a/demo.nsi b/demo.nsi index 261671b..32da9ad 100644 --- a/demo.nsi +++ b/demo.nsi @@ -25,7 +25,7 @@ Section nsis_process::FindProcess "abcdef.exe" Pop $1 DetailPrint $1 - nsis_download::Download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "wv2setup.exe" - Pop $1 - DetailPrint $1 + ; nsis_download::Download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "wv2setup.exe" + ; Pop $1 + ; DetailPrint $1 SectionEnd \ No newline at end of file From 39fd56ca8276aedd84daf02e7e51d76ae2ed2cf8 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Sun, 28 Apr 2024 02:45:25 +0300 Subject: [PATCH 3/8] bring back error type --- crates/nsis-fn/src/lib.rs | 12 +++++- crates/nsis-plugin-api/src/lib.rs | 55 +++++++++++++++++++++------- crates/nsis-process/src/lib.rs | 35 +++++++++--------- crates/nsis-semvercompare/src/lib.rs | 14 ++++--- demo.nsi | 3 -- 5 files changed, 77 insertions(+), 42 deletions(-) diff --git a/crates/nsis-fn/src/lib.rs b/crates/nsis-fn/src/lib.rs index 63f55e3..d6a4bb5 100644 --- a/crates/nsis-fn/src/lib.rs +++ b/crates/nsis-fn/src/lib.rs @@ -1,6 +1,7 @@ use proc_macro::TokenStream; +use proc_macro2::Span; use quote::quote; -use syn::{parse::Parse, parse_macro_input, ItemFn}; +use syn::{parse::Parse, parse_macro_input, Ident, ItemFn}; struct NsisFn { func: ItemFn, @@ -22,7 +23,12 @@ pub fn nsis_fn(_attr: TokenStream, tokens: TokenStream) -> TokenStream { let block = func.block; let attrs = func.attrs; + let new_ident = Ident::new(&format!("__{}", ident.to_string()), Span::call_site()); + quote! { + #[inline(always)] + pub unsafe fn #new_ident() -> Result<(), ::nsis_plugin_api::Error> #block + #(#attrs)* #[no_mangle] #[allow(non_standard_style)] @@ -33,7 +39,9 @@ pub fn nsis_fn(_attr: TokenStream, tokens: TokenStream) -> TokenStream { stacktop: *mut *mut ::nsis_plugin_api::stack_t, ) { ::nsis_plugin_api::exdll_init(string_size, variables, stacktop); - #block + if let Err(e) = #new_ident() { + e.push_err(); + } } } .into() diff --git a/crates/nsis-plugin-api/src/lib.rs b/crates/nsis-plugin-api/src/lib.rs index d56ea78..e1c3305 100644 --- a/crates/nsis-plugin-api/src/lib.rs +++ b/crates/nsis-plugin-api/src/lib.rs @@ -37,10 +37,11 @@ pub struct stack_t { pub text: [wchar_t; 1], } -static mut G_STRINGSIZE: c_int = 0; -static mut G_VARIABLES: *mut wchar_t = core::ptr::null_mut(); -static mut G_STACKTOP: *mut *mut stack_t = core::ptr::null_mut(); +pub static mut G_STRINGSIZE: c_int = 0; +pub static mut G_VARIABLES: *mut wchar_t = core::ptr::null_mut(); +pub static mut G_STACKTOP: *mut *mut stack_t = core::ptr::null_mut(); +#[inline(always)] pub unsafe fn exdll_init(string_size: c_int, variables: *mut wchar_t, stacktop: *mut *mut stack_t) { G_STRINGSIZE = string_size; G_VARIABLES = variables; @@ -51,9 +52,27 @@ pub const ONE: [u16; 2] = [49, 0]; pub const ZERO: [u16; 2] = [48, 0]; pub const NEGATIVE_ONE: [u16; 3] = [45, 49, 0]; -pub unsafe fn push(bytes: &[u16]) { +#[derive(Debug)] +pub enum Error { + StackIsNull, + ParseIntError, +} + +impl Error { + const fn description(&self) -> &str { + match self { + Error::StackIsNull => "Stack is null", + Error::ParseIntError => "Failed to parse integer", + } + } + pub fn push_err(&self) { + let _ = unsafe { pushstr(&self.description()) }; + } +} + +pub unsafe fn push(bytes: &[u16]) -> Result<(), Error> { if G_STACKTOP.is_null() { - return; + return Err(Error::StackIsNull); } let n = size_of::() + G_STRINGSIZE as usize * 2; @@ -61,33 +80,43 @@ pub unsafe fn push(bytes: &[u16]) { lstrcpyW((*th).text.as_ptr() as _, bytes.as_ptr()); (*th).next = *G_STACKTOP; *G_STACKTOP = th; + + Ok(()) } -pub unsafe fn pushstr(str: &str) { +pub unsafe fn pushstr(str: &str) -> Result<(), Error> { let bytes = encode_wide(str); push(&bytes) } -pub unsafe fn pushint(int: i32) { +pub unsafe fn pushint(int: i32) -> Result<(), Error> { let str = int.to_string(); pushstr(&str) } -pub unsafe fn pop(out: &mut [u16]) { +pub unsafe fn pop() -> Result, Error> { if G_STACKTOP.is_null() || (*G_STACKTOP).is_null() { - return; + return Err(Error::StackIsNull); } + let mut out = vec![0_u16; G_STRINGSIZE as _]; + let th: *mut stack_t = *G_STACKTOP; lstrcpyW(out.as_mut_ptr(), (*th).text.as_ptr() as _); *G_STACKTOP = (*th).next; GlobalFree(th as _); + + Ok(out) +} + +pub unsafe fn popstr() -> Result { + let bytes = pop()?; + Ok(decode_wide(&bytes)) } -pub unsafe fn popstring() -> String { - let mut bytes = vec![0_u16; G_STRINGSIZE as _]; - pop(&mut bytes); - decode_wide(&bytes) +pub unsafe fn popint() -> Result { + let str = popstr()?; + str.parse().map_err(|_| Error::ParseIntError) } pub fn encode_wide(str: &str) -> Vec { diff --git a/crates/nsis-process/src/lib.rs b/crates/nsis-process/src/lib.rs index bbedb79..2a945ef 100644 --- a/crates/nsis-process/src/lib.rs +++ b/crates/nsis-process/src/lib.rs @@ -30,13 +30,13 @@ nsis_plugin!(); /// /// This function always expects 1 string on the stack ($1: name) and will panic otherwise. #[nsis_fn] -fn FindProcess() { - let name = popstring(); +fn FindProcess() -> Result<(), Error> { + let name = popstr()?; if !get_processes(&name).is_empty() { - push(&ZERO); + push(&ZERO) } else { - push(&ONE); + push(&ONE) } } @@ -46,8 +46,8 @@ fn FindProcess() { /// /// This function always expects 1 string on the stack ($1: name) and will panic otherwise. #[nsis_fn] -fn FindProcessCurrentUser() { - let name = popstring(); +fn FindProcessCurrentUser() -> Result<(), Error> { + let name = popstr()?; let processes = get_processes(&name); @@ -56,15 +56,15 @@ fn FindProcessCurrentUser() { .into_iter() .any(|pid| belongs_to_user(user_sid, pid)) { - push(&ZERO); + push(&ZERO) } else { - push(&ONE); + push(&ONE) } // Fall back to perMachine checks if we can't get current user id } else if processes.is_empty() { - push(&ONE); + push(&ONE) } else { - push(&ZERO); + push(&ZERO) } } @@ -74,15 +74,15 @@ fn FindProcessCurrentUser() { /// /// This function always expects 1 string on the stack ($1: name) and will panic otherwise. #[nsis_fn] -fn KillProcess() { - let name = popstring(); +fn KillProcess() -> Result<(), Error> { + let name = popstr()?; let processes = get_processes(&name); if !processes.is_empty() && processes.into_iter().map(kill).all(|b| b) { - push(&ZERO); + push(&ZERO) } else { - push(&ONE); + push(&ONE) } } @@ -92,14 +92,13 @@ fn KillProcess() { /// /// This function always expects 1 string on the stack ($1: name) and will panic otherwise. #[nsis_fn] -fn KillProcessCurrentUser() { - let name = popstring(); +fn KillProcessCurrentUser() -> Result<(), Error> { + let name = popstr()?; let processes = get_processes(&name); if processes.is_empty() { - push(&ONE); - return; + return push(&ONE); } let success = if let Some(user_sid) = get_sid(GetCurrentProcessId()) { diff --git a/crates/nsis-semvercompare/src/lib.rs b/crates/nsis-semvercompare/src/lib.rs index 171d69b..885b3a6 100644 --- a/crates/nsis-semvercompare/src/lib.rs +++ b/crates/nsis-semvercompare/src/lib.rs @@ -15,16 +15,18 @@ nsis_plugin!(); /// /// This function always expects 2 strings on the stack ($v1, $v2) and will panic otherwise. #[nsis_fn] -fn SemverCompare() { - let v1 = popstring(); - let v2 = popstring(); +fn SemverCompare() -> Result<(), Error> { + let v1 = popstr()?; + let v2 = popstr()?; match compare(&v1, &v2) { - -1 => push(&NEGATIVE_ONE), - 0 => push(&ZERO), - 1 => push(&ONE), + -1 => push(&NEGATIVE_ONE)?, + 0 => push(&ZERO)?, + 1 => push(&ONE)?, _ => unreachable!(), } + + Ok(()) } fn compare(v1: &str, v2: &str) -> i32 { diff --git a/demo.nsi b/demo.nsi index 32da9ad..b7b8faf 100644 --- a/demo.nsi +++ b/demo.nsi @@ -4,11 +4,8 @@ Unicode true ShowInstDetails show !addplugindir ".\target\i686-pc-windows-msvc\release" -!addplugindir ".\target\i686-pc-windows-msvc\debug" !addplugindir "$%CARGO_TARGET_DIR%\i686-pc-windows-msvc\release" -!addplugindir "$%CARGO_TARGET_DIR%\i686-pc-windows-msvc\debug" !addplugindir "$%CARGO_BUILD_TARGET_DIR%\i686-pc-windows-msvc\release" -!addplugindir "$%CARGO_BUILD_TARGET_DIR%\i686-pc-windows-msvc\debug" !include "MUI2.nsh" From b34947617e894a4a0b24cc0266a23c040d94e031 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Mon, 29 Apr 2024 02:54:16 +0300 Subject: [PATCH 4/8] remove download plugin --- Cargo.toml | 1 - README.md | 13 +- crates/nsis-download/CHANGELOG.md | 16 --- crates/nsis-download/Cargo.toml | 15 --- crates/nsis-download/src/lib.rs | 199 ------------------------------ demo.nsi | 9 +- 6 files changed, 9 insertions(+), 244 deletions(-) delete mode 100644 crates/nsis-download/CHANGELOG.md delete mode 100644 crates/nsis-download/Cargo.toml delete mode 100644 crates/nsis-download/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index bf7a5ae..8b115bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = ["crates/*"] -exclude = ["crates/nsis-download"] resolver = "2" [workspace.package] diff --git a/README.md b/README.md index d2993ec..673a6ae 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,12 @@ A collection of NSIS plugins written in rust. -| Plugin | Description | -|---|---| -| [nsis-download](./crates/nsis-download/) | Download a file from an URL to a path | -| [nsis-process](./crates/nsis-process/) | Find and Kill processes | -| [nsis-semvercompare](./crates/nsis-semvercompare/) | Compare two semantic versions | -| [nsis-tauri-utils](./crates/nsis-tauri-utils/) | A collection of all the above plugins into a single DLL for smaller size | +| Plugin | Description | +| -------------------------------------------------- | ------------------------------------------------------------------------ | +| [nsis-process](./crates/nsis-process/) | Find and Kill processes | +| [nsis-semvercompare](./crates/nsis-semvercompare/) | Compare two semantic versions | +| [nsis-tauri-utils](./crates/nsis-tauri-utils/) | A collection of all the above plugins into a single DLL for smaller size | ## License -Apache-2.0/MIT \ No newline at end of file +Apache-2.0/MIT diff --git a/crates/nsis-download/CHANGELOG.md b/crates/nsis-download/CHANGELOG.md deleted file mode 100644 index 4ff477a..0000000 --- a/crates/nsis-download/CHANGELOG.md +++ /dev/null @@ -1,16 +0,0 @@ -# Changelog - -## \[0.3.0] - -- [`33ea4bc`](https://www.github.com/tauri-apps/nsis-tauri-utils/commit/33ea4bcf2a573461ebc5181ef2921d8746005049)([#17](https://www.github.com/tauri-apps/nsis-tauri-utils/pull/17)) Statically link CRT. - -## \[0.2.0] - -- Add download progress bar - - [eba1392](https://www.github.com/tauri-apps/nsis-tauri-utils/commit/eba1392081d22879383ba1e21c6b7bceb19a42f2) feat(download): add progress bar ([#8](https://www.github.com/tauri-apps/nsis-tauri-utils/pull/8)) on 2023-01-24 - - [f048814](https://www.github.com/tauri-apps/nsis-tauri-utils/commit/f048814ba73b0f7436e9e25bb9cb0885e8e05fef) chore: update bump to minor on 2023-01-24 - -## \[0.1.0] - -- Initial Release. - - [000d632](https://www.github.com/tauri-apps/nsis-tauri-utils/commit/000d6326333f862741f1514de34542316445951e) ci: setup CI/CD and covector ([#2](https://www.github.com/tauri-apps/nsis-tauri-utils/pull/2)) on 2023-01-21 diff --git a/crates/nsis-download/Cargo.toml b/crates/nsis-download/Cargo.toml deleted file mode 100644 index 9240a72..0000000 --- a/crates/nsis-download/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "nsis-download" -version = "0.3.0" -authors = { workspace = true } -edition = { workspace = true } -license = { workspace = true } - -[lib] -crate-type = ["rlib", "cdylib"] - -[dependencies] -ureq = { version = "2", default-features = false, features = ["tls"] } -progress-streams = "1.1" -nsis-plugin-api = { workspace = true } -windows-sys = { workspace = true } diff --git a/crates/nsis-download/src/lib.rs b/crates/nsis-download/src/lib.rs deleted file mode 100644 index 9051c0f..0000000 --- a/crates/nsis-download/src/lib.rs +++ /dev/null @@ -1,199 +0,0 @@ -use std::{fs, io, path::Path}; - -use nsis_plugin_api::*; -use progress_streams::ProgressReader; -use windows_sys::Win32::{ - Foundation::HWND, - UI::{ - Controls::{PBM_SETPOS, PROGRESS_CLASSW, WC_STATICW}, - WindowsAndMessaging::{ - CreateWindowExW, FindWindowExW, GetWindowLongPtrW, SendMessageW, SetWindowPos, - SetWindowTextW, ShowWindow, GWL_STYLE, SWP_FRAMECHANGED, SWP_NOSIZE, SW_HIDE, - WM_GETFONT, WM_SETFONT, WS_CHILD, WS_VISIBLE, - }, - }, -}; - -/// Download a file from an URL to a path. -/// -/// # Safety -/// -/// This function always expects 2 strings on the stack ($1: url, $2: path) and will panic otherwise. -#[nsis_fn] -fn Download() { - exdll_init(string_size, variables, stacktop); - - let url = popstring(); - let path = popstring(); - - let status = download_file(hwnd_parent, &url, &path); - pushint(status); -} - -fn download_file(hwnd_parent: HWND, url: &str, path: &str) -> i32 { - let childhwnd; - let mut progress_bar: HWND = 0; - let mut progress_text: HWND = 0; - let mut downloading_text: HWND = 0; - let mut details_section: HWND = 0; - let mut details_section_resized = false; - let mut details_section_resized_back = false; - - if hwnd_parent != 0 { - childhwnd = find_window(hwnd_parent, "#32770"); - if childhwnd != 0 { - details_section = find_window(childhwnd, "SysListView32"); - let expanded = is_visible(details_section); - unsafe { - progress_bar = CreateWindowExW( - 0, - PROGRESS_CLASSW, - std::ptr::null(), - WS_CHILD | WS_VISIBLE, - 0, - if expanded { 40 } else { 75 }, - 450, - 18, - childhwnd, - 0, - 0, - std::ptr::null(), - ); - - downloading_text = CreateWindowExW( - 0, - WC_STATICW, - std::ptr::null(), - WS_CHILD | WS_VISIBLE, - 0, - if expanded { 60 } else { 95 }, - 450, - 18, - childhwnd, - 0, - 0, - std::ptr::null(), - ); - - progress_text = CreateWindowExW( - 0, - WC_STATICW, - std::ptr::null(), - WS_CHILD | WS_VISIBLE, - 0, - if expanded { 78 } else { 113 }, - 450, - 18, - childhwnd, - 0, - 0, - std::ptr::null(), - ); - - let font = SendMessageW(childhwnd, WM_GETFONT, 0, 0); - SendMessageW(downloading_text, WM_SETFONT, font as _, 0); - SendMessageW(progress_text, WM_SETFONT, font as _, 0); - }; - } - } - - let response = match ureq::get(url).call() { - Ok(data) => data, - Err(err) => { - return match err { - ureq::Error::Status(code, _) => code as i32, - ureq::Error::Transport(_) => 499, - } - } - }; - - let total = response - .header("Content-Length") - .unwrap_or("0") - .parse::() - .unwrap(); - - let mut read = 0; - - let mut reader = response.into_reader(); - let mut reader = ProgressReader::new(&mut reader, |progress: usize| { - let expanded = is_visible(details_section); - if expanded && !details_section_resized { - unsafe { - SetWindowPos(progress_bar, 0, 0, 40, 0, 0, SWP_NOSIZE); - SetWindowPos(downloading_text, 0, 0, 60, 0, 0, SWP_NOSIZE); - SetWindowPos(progress_text, 0, 0, 78, 0, 0, SWP_NOSIZE); - - SetWindowPos(details_section, 0, 0, 100, 450, 120, SWP_FRAMECHANGED); - } - details_section_resized = true; - } - - read += progress; - - let percentage = (read as f64 / total as f64) * 100.0; - unsafe { SendMessageW(progress_bar, PBM_SETPOS, percentage as _, 0) }; - - let text = encode_wide(&format!( - "{} / {} KiB - {:.2}%", - read / 1024, - total / 1024, - percentage, - )); - unsafe { SetWindowTextW(progress_text, text.as_ptr()) }; - - let text = encode_wide(&format!("Downloading {} ...", url)); - unsafe { SetWindowTextW(downloading_text, text.as_ptr()) }; - - if percentage >= 100. && !details_section_resized_back { - unsafe { - ShowWindow(progress_bar, SW_HIDE); - ShowWindow(progress_text, SW_HIDE); - ShowWindow(downloading_text, SW_HIDE); - SetWindowPos(details_section, 0, 0, 41, 450, 180, SWP_FRAMECHANGED); - } - details_section_resized_back = true; - } - }); - - let path = Path::new(path); - fs::create_dir_all(path.parent().unwrap_or_else(|| Path::new("."))).unwrap(); - - let mut file = fs::File::options() - .create(true) - .write(true) - .truncate(true) - .open(path) - .unwrap(); - - let res = io::copy(&mut reader, &mut file); - - i32::from(res.is_err()) -} - -fn find_window(parent: HWND, class: impl AsRef) -> HWND { - let class = encode_wide(class.as_ref()); - unsafe { FindWindowExW(parent, 0, class.as_ptr(), std::ptr::null()) } -} - -fn is_visible(hwnd: HWND) -> bool { - let style = unsafe { GetWindowLongPtrW(hwnd, GWL_STYLE) }; - (style & !WS_VISIBLE as i32) != style -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_downloads() { - assert_eq!( - download_file( - 0, - "https://go.microsoft.com/fwlink/p/?LinkId=2124703", - "wv2setup.exe" - ), - 0 - ) - } -} diff --git a/demo.nsi b/demo.nsi index b7b8faf..b1f9c94 100644 --- a/demo.nsi +++ b/demo.nsi @@ -15,14 +15,11 @@ ShowInstDetails show Section nsis_semvercompare::SemverCompare "1.0.0" "1.1.0" Pop $1 - DetailPrint $1 + DetailPrint "SemverCompare(1.0.0, 1.1.0): $1" nsis_process::FindProcess "explorer.exe" Pop $1 - DetailPrint $1 + DetailPrint "FindProcess(explorer.exe): $1" nsis_process::FindProcess "abcdef.exe" Pop $1 - DetailPrint $1 - ; nsis_download::Download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "wv2setup.exe" - ; Pop $1 - ; DetailPrint $1 + DetailPrint "FindProcess(abcdef.exe): $1" SectionEnd \ No newline at end of file From 9c3c46f07b4e570fb768422da6c52f5ea122154f Mon Sep 17 00:00:00 2001 From: amrbashir Date: Mon, 29 Apr 2024 20:45:25 +0300 Subject: [PATCH 5/8] change file --- .changes/no_std.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changes/no_std.md diff --git a/.changes/no_std.md b/.changes/no_std.md new file mode 100644 index 0000000..c8849b8 --- /dev/null +++ b/.changes/no_std.md @@ -0,0 +1,7 @@ +--- +"nsis_tauri_utils": "minor" +"nsis_semvercompare": "minor" +"nsis_process": "minor" +--- + +Reduce the DLL size by using `no_std` and without static msvcrt. From a6f9324a87c4d3c18a51ba6421a41d293a31bef0 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Mon, 29 Apr 2024 20:50:25 +0300 Subject: [PATCH 6/8] Update Cargo.toml Co-authored-by: Fabian-Lars --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8b115bb..c6d62d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,6 @@ features = [ [profile.release] panic = "abort" # Strip expensive panic clean-up logic codegen-units = 1 # Compile crates one after another so the compiler can optimize better -lto = true # Enables link to optimizations +lto = true # Enables link time optimizations opt-level = "s" # Optimize for binary size strip = true # Remove debug symbols From 3dd7d1620e447957adcb8a5d0b7d9b28e86a15e7 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Mon, 29 Apr 2024 21:26:42 +0300 Subject: [PATCH 7/8] fix clippy and update CI --- .github/workflows/audit.yml | 5 +- .github/workflows/change-status-on-pr.yml | 2 +- .github/workflows/clippy-fmt.yml | 30 ++------- .../workflows/covector-version-or-publish.yml | 9 +-- .github/workflows/test.yml | 4 +- crates/nsis-fn/src/lib.rs | 5 +- crates/nsis-plugin-api/src/lib.rs | 67 ++++++++++++++----- crates/nsis-process/src/lib.rs | 24 +++---- crates/nsis-semvercompare/src/lib.rs | 6 +- crates/nsis-tauri-utils/build.rs | 30 ++++++--- 10 files changed, 104 insertions(+), 78 deletions(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 184b5af..d2531d1 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -27,8 +27,7 @@ jobs: audit: runs-on: windows-latest steps: - - uses: actions/checkout@v2 - - name: rust audit - uses: actions-rs/audit-check@v1 + - uses: actions/checkout@v4 + - uses: rustsec/audit-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/change-status-on-pr.yml b/.github/workflows/change-status-on-pr.yml index 44f842c..e0d84df 100644 --- a/.github/workflows/change-status-on-pr.yml +++ b/.github/workflows/change-status-on-pr.yml @@ -10,7 +10,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: covector status diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index c72d413..377629d 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -18,33 +18,17 @@ jobs: clippy: runs-on: windows-latest steps: - - uses: actions/checkout@v3 - - name: install stable - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - profile: minimal - toolchain: stable - override: true components: clippy + - run: cargo clippy --release --all-targets --all-features -- -D warnings - - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --all-targets --all-features -- -D warnings - - fmt: + rustfmt: runs-on: windows-latest steps: - - uses: actions/checkout@v3 - - name: install stable - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - profile: minimal - toolchain: stable - override: true components: rustfmt - - - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check \ No newline at end of file + - run: cargo fmt --all -- --check \ No newline at end of file diff --git a/.github/workflows/covector-version-or-publish.yml b/.github/workflows/covector-version-or-publish.yml index b301a21..a0cce04 100644 --- a/.github/workflows/covector-version-or-publish.yml +++ b/.github/workflows/covector-version-or-publish.yml @@ -19,16 +19,11 @@ jobs: successfulPublish: ${{ steps.covector.outputs.successfulPublish }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Setup node - uses: actions/setup-node@v3 - with: - node-version: 16 - check-latest: true - registry-url: 'https://registry.npmjs.org' + - uses: actions/setup-node@v4 - name: git config run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 55cbb3e..e18f0ad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: install stable uses: actions-rs/toolchain@v1 @@ -33,4 +33,4 @@ jobs: - uses: actions-rs/cargo@v1 with: - command: test \ No newline at end of file + command: test --release \ No newline at end of file diff --git a/crates/nsis-fn/src/lib.rs b/crates/nsis-fn/src/lib.rs index d6a4bb5..885fb27 100644 --- a/crates/nsis-fn/src/lib.rs +++ b/crates/nsis-fn/src/lib.rs @@ -14,6 +14,9 @@ impl Parse for NsisFn { } } +/// Generates a wrapper NSIS compliant dll export that calls `nsis_plugin_api::exdll_init` +/// automatically. This macro expects the function to return a `Result<(), nsis_plugin_api::Error>` +/// and will automatically push the error to NSIS stack on failure. #[proc_macro_attribute] pub fn nsis_fn(_attr: TokenStream, tokens: TokenStream) -> TokenStream { let tokens = parse_macro_input!(tokens as NsisFn); @@ -23,7 +26,7 @@ pub fn nsis_fn(_attr: TokenStream, tokens: TokenStream) -> TokenStream { let block = func.block; let attrs = func.attrs; - let new_ident = Ident::new(&format!("__{}", ident.to_string()), Span::call_site()); + let new_ident = Ident::new(&format!("__{}", ident), Span::call_site()); quote! { #[inline(always)] diff --git a/crates/nsis-plugin-api/src/lib.rs b/crates/nsis-plugin-api/src/lib.rs index e1c3305..3f70612 100644 --- a/crates/nsis-plugin-api/src/lib.rs +++ b/crates/nsis-plugin-api/src/lib.rs @@ -41,6 +41,11 @@ pub static mut G_STRINGSIZE: c_int = 0; pub static mut G_VARIABLES: *mut wchar_t = core::ptr::null_mut(); pub static mut G_STACKTOP: *mut *mut stack_t = core::ptr::null_mut(); +/// Initis the global variables used by NSIS functions: [`push`], [`pushstr`], [`pushint`], [`pop`], [`popstr`] and [`popint`] +/// +/// # Safety +/// +/// This function mutates static variables and should only be called in a function #[inline(always)] pub unsafe fn exdll_init(string_size: c_int, variables: *mut wchar_t, stacktop: *mut *mut stack_t) { G_STRINGSIZE = string_size; @@ -48,9 +53,9 @@ pub unsafe fn exdll_init(string_size: c_int, variables: *mut wchar_t, stacktop: G_STACKTOP = stacktop; } -pub const ONE: [u16; 2] = [49, 0]; -pub const ZERO: [u16; 2] = [48, 0]; -pub const NEGATIVE_ONE: [u16; 3] = [45, 49, 0]; +pub const ONE: &[u16; 2] = &[49, 0]; +pub const ZERO: &[u16; 2] = &[48, 0]; +pub const NEGATIVE_ONE: &[u16; 3] = &[45, 49, 0]; #[derive(Debug)] pub enum Error { @@ -66,10 +71,15 @@ impl Error { } } pub fn push_err(&self) { - let _ = unsafe { pushstr(&self.description()) }; + let _ = unsafe { pushstr(self.description()) }; } } +/// Pushes some bytes onto the NSIS stack. +/// +/// # Safety +/// +/// This function reads static variables and should only be called after [`exdll_init`] is called. pub unsafe fn push(bytes: &[u16]) -> Result<(), Error> { if G_STACKTOP.is_null() { return Err(Error::StackIsNull); @@ -84,16 +94,31 @@ pub unsafe fn push(bytes: &[u16]) -> Result<(), Error> { Ok(()) } +/// Pushes a string onto the NSIS stack. +/// +/// # Safety +/// +/// This function reads static variables and should only be called after [`exdll_init`] is called. pub unsafe fn pushstr(str: &str) -> Result<(), Error> { - let bytes = encode_wide(str); + let bytes = encode_utf16(str); push(&bytes) } +/// Pushes an integer onto the NSIS stack. +/// +/// # Safety +/// +/// This function reads static variables and should only be called after [`exdll_init`] is called. pub unsafe fn pushint(int: i32) -> Result<(), Error> { let str = int.to_string(); pushstr(&str) } +/// Pops bytes from NSIS stack. +/// +/// # Safety +/// +/// This function reads static variables and should only be called after [`exdll_init`] is called. pub unsafe fn pop() -> Result, Error> { if G_STACKTOP.is_null() || (*G_STACKTOP).is_null() { return Err(Error::StackIsNull); @@ -109,28 +134,38 @@ pub unsafe fn pop() -> Result, Error> { Ok(out) } +/// Pops a string from NSIS stack. +/// +/// # Safety +/// +/// This function reads static variables and should only be called after [`exdll_init`] is called. pub unsafe fn popstr() -> Result { let bytes = pop()?; - Ok(decode_wide(&bytes)) + Ok(decode_utf16_lossy(&bytes)) } +/// Pops an integer from NSIS stack. +/// +/// # Safety +/// +/// This function reads static variables and should only be called after [`exdll_init`] is called. pub unsafe fn popint() -> Result { let str = popstr()?; str.parse().map_err(|_| Error::ParseIntError) } -pub fn encode_wide(str: &str) -> Vec { +pub fn encode_utf16(str: &str) -> Vec { str.encode_utf16() .chain(iter::once(0)) .collect::>() } -pub fn decode_wide(bytes: &[u16]) -> String { +pub fn decode_utf16_lossy(bytes: &[u16]) -> String { let bytes = bytes .iter() .position(|c| *c == 0) .map(|nul| &bytes[..nul]) - .unwrap_or(&bytes); + .unwrap_or(bytes); String::from_utf16_lossy(bytes) } @@ -183,21 +218,21 @@ macro_rules! nsis_plugin { } #[no_mangle] - pub unsafe extern "C" fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 { + pub unsafe extern "C" fn memcpy(dest: *mut u8, src: *const u8, n: isize) -> *mut u8 { let mut i = 0; while i < n { - *dest.offset(i as isize) = *src.offset(i as isize); + *dest.offset(i) = *src.offset(i); i += 1; } return dest; } #[no_mangle] - pub unsafe extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 { + pub unsafe extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: isize) -> i32 { let mut i = 0; while i < n { - let a = *s1.offset(i as isize); - let b = *s2.offset(i as isize); + let a = *s1.offset(i); + let b = *s2.offset(i); if a != b { return a as i32 - b as i32; } @@ -207,10 +242,10 @@ macro_rules! nsis_plugin { } #[no_mangle] - pub unsafe extern "C" fn memset(s: *mut u8, c: i32, n: usize) -> *mut u8 { + pub unsafe extern "C" fn memset(s: *mut u8, c: i32, n: isize) -> *mut u8 { let mut i = 0; while i < n { - *s.offset(i as isize) = c as u8; + *s.offset(i) = c as u8; i += 1; } return s; diff --git a/crates/nsis-process/src/lib.rs b/crates/nsis-process/src/lib.rs index 2a945ef..3fd6250 100644 --- a/crates/nsis-process/src/lib.rs +++ b/crates/nsis-process/src/lib.rs @@ -34,9 +34,9 @@ fn FindProcess() -> Result<(), Error> { let name = popstr()?; if !get_processes(&name).is_empty() { - push(&ZERO) + push(ZERO) } else { - push(&ONE) + push(ONE) } } @@ -56,15 +56,15 @@ fn FindProcessCurrentUser() -> Result<(), Error> { .into_iter() .any(|pid| belongs_to_user(user_sid, pid)) { - push(&ZERO) + push(ZERO) } else { - push(&ONE) + push(ONE) } // Fall back to perMachine checks if we can't get current user id } else if processes.is_empty() { - push(&ONE) + push(ONE) } else { - push(&ZERO) + push(ZERO) } } @@ -80,9 +80,9 @@ fn KillProcess() -> Result<(), Error> { let processes = get_processes(&name); if !processes.is_empty() && processes.into_iter().map(kill).all(|b| b) { - push(&ZERO) + push(ZERO) } else { - push(&ONE) + push(ONE) } } @@ -98,7 +98,7 @@ fn KillProcessCurrentUser() -> Result<(), Error> { let processes = get_processes(&name); if processes.is_empty() { - return push(&ONE); + return push(ONE); } let success = if let Some(user_sid) = get_sid(GetCurrentProcessId()) { @@ -112,9 +112,9 @@ fn KillProcessCurrentUser() -> Result<(), Error> { }; if success { - push(&ZERO) + push(ZERO) } else { - push(&ONE) + push(ONE) } } @@ -196,7 +196,7 @@ fn get_processes(name: &str) -> Vec { if Process32FirstW(handle, &mut process) != 0 { while Process32NextW(handle, &mut process) != 0 { if current_pid != process.th32ProcessID - && decode_wide(&process.szExeFile).to_lowercase() == name.to_lowercase() + && decode_utf16_lossy(&process.szExeFile).to_lowercase() == name.to_lowercase() { processes.push(process.th32ProcessID); } diff --git a/crates/nsis-semvercompare/src/lib.rs b/crates/nsis-semvercompare/src/lib.rs index 885b3a6..d8bca47 100644 --- a/crates/nsis-semvercompare/src/lib.rs +++ b/crates/nsis-semvercompare/src/lib.rs @@ -20,9 +20,9 @@ fn SemverCompare() -> Result<(), Error> { let v2 = popstr()?; match compare(&v1, &v2) { - -1 => push(&NEGATIVE_ONE)?, - 0 => push(&ZERO)?, - 1 => push(&ONE)?, + -1 => push(NEGATIVE_ONE)?, + 0 => push(ZERO)?, + 1 => push(ONE)?, _ => unreachable!(), } diff --git a/crates/nsis-tauri-utils/build.rs b/crates/nsis-tauri-utils/build.rs index cb35723..8243482 100644 --- a/crates/nsis-tauri-utils/build.rs +++ b/crates/nsis-tauri-utils/build.rs @@ -1,9 +1,14 @@ fn main() { - write_plugins(); + combine_plugins_and_write_to_out_dir(); println!("cargo::rustc-link-arg=/ENTRY:DllMain") } -fn write_plugins() { +/// Combines the plugins into one file that is included in lib.rs +/// using `include!(concat!(env!("OUT_DIR"), "/combined_libs.rs"));` +/// +/// Plugins are combined this way because it saves a few kilobytes in the generated DLL +/// than the making nsis-tauri-utils depend on other plugins and re-export the DLLs +fn combine_plugins_and_write_to_out_dir() { let out_dir = std::env::var("OUT_DIR").unwrap(); let path = format!("{out_dir}/combined_libs.rs"); @@ -11,22 +16,27 @@ fn write_plugins() { .truncate(true) .write(true) .create(true) - .open(&path) + .open(path) .unwrap(); - let p1 = include_str!("../nsis-semvercompare/src/lib.rs"); - let p2 = include_str!("../nsis-process/src/lib.rs"); - for p in [p1, p2] { - let lines = p + for plugin in [ + include_str!("../nsis-semvercompare/src/lib.rs"), + include_str!("../nsis-process/src/lib.rs"), + ] { + let lines = plugin .lines() .filter(|l| { + // remove lines that should only be specified once + // either for compilation or for clippy !(l.contains("#![no_std]") - || l.contains("use nsis_plugin_api::*;") - || l.contains("nsis_plugin!();")) + || l.contains("nsis_plugin!();") + || l.contains("use nsis_plugin_api::*;")) }) + .take_while(|l| !l.contains("mod tests {")) .collect::>(); - let content = lines.join("\n"); + // skip last line which should be #[cfg(test)] + let content = lines[..lines.len() - 1].join("\n"); std::io::Write::write_all(&mut file, content.as_bytes()).unwrap(); } } From e00c436b98818e3f3e27e1a1dec7f734748a9ff5 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Tue, 30 Apr 2024 00:08:57 +0300 Subject: [PATCH 8/8] fix test workflow --- .github/workflows/test.yml | 13 ++----------- crates/nsis-process/Cargo.toml | 3 +++ crates/nsis-process/build.rs | 4 +++- crates/nsis-semvercompare/Cargo.toml | 3 +++ crates/nsis-semvercompare/build.rs | 4 +++- crates/nsis-tauri-utils/Cargo.toml | 3 +++ crates/nsis-tauri-utils/build.rs | 4 +++- 7 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e18f0ad..cb7f712 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,14 +23,5 @@ jobs: steps: - uses: actions/checkout@v4 - - - name: install stable - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - - uses: actions-rs/cargo@v1 - with: - command: test --release \ No newline at end of file + - uses: dtolnay/rust-toolchain@stable + - run: cargo test --features test \ No newline at end of file diff --git a/crates/nsis-process/Cargo.toml b/crates/nsis-process/Cargo.toml index 61ed417..a0cd6fa 100644 --- a/crates/nsis-process/Cargo.toml +++ b/crates/nsis-process/Cargo.toml @@ -8,6 +8,9 @@ license = { workspace = true } [lib] crate-type = ["cdylib"] +[features] +test = [] + [dependencies] nsis-plugin-api = { workspace = true } windows-sys = { workspace = true } diff --git a/crates/nsis-process/build.rs b/crates/nsis-process/build.rs index f27bc53..ce8d102 100644 --- a/crates/nsis-process/build.rs +++ b/crates/nsis-process/build.rs @@ -1,3 +1,5 @@ fn main() { - println!("cargo::rustc-link-arg=/ENTRY:DllMain") + if std::env::var("CARGO_FEATURE_TEST").as_deref() != Ok("1") { + println!("cargo::rustc-link-arg=/ENTRY:DllMain") + } } diff --git a/crates/nsis-semvercompare/Cargo.toml b/crates/nsis-semvercompare/Cargo.toml index e1a5469..15e9f95 100644 --- a/crates/nsis-semvercompare/Cargo.toml +++ b/crates/nsis-semvercompare/Cargo.toml @@ -8,6 +8,9 @@ license = { workspace = true } [lib] crate-type = ["cdylib"] +[features] +test = [] + [dependencies] semver = { version = "1.0", default-features = false } nsis-plugin-api = { workspace = true } diff --git a/crates/nsis-semvercompare/build.rs b/crates/nsis-semvercompare/build.rs index f27bc53..ce8d102 100644 --- a/crates/nsis-semvercompare/build.rs +++ b/crates/nsis-semvercompare/build.rs @@ -1,3 +1,5 @@ fn main() { - println!("cargo::rustc-link-arg=/ENTRY:DllMain") + if std::env::var("CARGO_FEATURE_TEST").as_deref() != Ok("1") { + println!("cargo::rustc-link-arg=/ENTRY:DllMain") + } } diff --git a/crates/nsis-tauri-utils/Cargo.toml b/crates/nsis-tauri-utils/Cargo.toml index 56d3c73..40918a4 100644 --- a/crates/nsis-tauri-utils/Cargo.toml +++ b/crates/nsis-tauri-utils/Cargo.toml @@ -8,6 +8,9 @@ license = { workspace = true } [lib] crate-type = ["cdylib"] +[features] +test = [] + [dependencies] nsis-plugin-api = { workspace = true } windows-sys = { workspace = true } diff --git a/crates/nsis-tauri-utils/build.rs b/crates/nsis-tauri-utils/build.rs index 8243482..3a7a7cf 100644 --- a/crates/nsis-tauri-utils/build.rs +++ b/crates/nsis-tauri-utils/build.rs @@ -1,6 +1,8 @@ fn main() { combine_plugins_and_write_to_out_dir(); - println!("cargo::rustc-link-arg=/ENTRY:DllMain") + if std::env::var("CARGO_FEATURE_TEST").as_deref() != Ok("1") { + println!("cargo::rustc-link-arg=/ENTRY:DllMain") + } } /// Combines the plugins into one file that is included in lib.rs