refactor!: no_std & no static msvcrt & remove download plugin (#26)

This commit is contained in:
Amr Bashir
2024-04-30 00:11:25 +03:00
committed by GitHub
parent 0c54d93a31
commit 5423579860
28 changed files with 501 additions and 516 deletions

View File

@@ -1,5 +1,2 @@
[build]
target = "i686-pc-windows-msvc"
[target.i686-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]

View File

@@ -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",

7
.changes/no_std.md Normal file
View File

@@ -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.

View File

@@ -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 }}

View File

@@ -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

View File

@@ -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
- run: cargo fmt --all -- --check

View File

@@ -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: |

View File

@@ -22,15 +22,6 @@ jobs:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: install stable
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: test
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo test --features test

View File

@@ -8,7 +8,7 @@ edition = "2021"
license = "MIT or Apache-2.0"
[workspace.dependencies]
pluginapi = { path = "./crates/pluginapi" }
nsis-plugin-api = { path = "./crates/nsis-plugin-api" }
[workspace.dependencies.windows-sys]
version = "0.52.0"
@@ -24,8 +24,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 time optimizations
opt-level = "s" # Optimize for binary size
strip = true # Remove debug symbols

View File

@@ -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
Apache-2.0/MIT

View File

@@ -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

View File

@@ -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"
pluginapi = { workspace = true }
windows-sys = { workspace = true }

View File

@@ -1,204 +0,0 @@
use std::{fs, io, path::Path};
use pluginapi::{exdll_init, popstring, pushint, stack_t, wchar_t};
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.
#[no_mangle]
pub unsafe extern "C" fn Download(
hwnd_parent: HWND,
string_size: u32,
variables: *mut wchar_t,
stacktop: *mut *mut stack_t,
) {
exdll_init(string_size, variables, stacktop);
let url = popstring().unwrap();
let path = popstring().unwrap();
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::<u128>()
.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 = pluginapi::encode_wide(format!(
"{} / {} KiB - {:.2}%",
read / 1024,
total / 1024,
percentage,
));
unsafe { SetWindowTextW(progress_text, text.as_ptr()) };
let text = pluginapi::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<str>) -> HWND {
let class = pluginapi::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
)
}
}

13
crates/nsis-fn/Cargo.toml Normal file
View File

@@ -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"] }

51
crates/nsis-fn/src/lib.rs Normal file
View File

@@ -0,0 +1,51 @@
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{parse::Parse, parse_macro_input, Ident, ItemFn};
struct NsisFn {
func: ItemFn,
}
impl Parse for NsisFn {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let func: ItemFn = input.parse()?;
Ok(Self { func })
}
}
/// 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);
let NsisFn { func } = tokens;
let ident = func.sig.ident;
let block = func.block;
let attrs = func.attrs;
let new_ident = Ident::new(&format!("__{}", ident), Span::call_site());
quote! {
#[inline(always)]
pub unsafe fn #new_ident() -> Result<(), ::nsis_plugin_api::Error> #block
#(#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);
if let Err(e) = #new_ident() {
e.push_err();
}
}
}
.into()
}

View File

@@ -1,8 +1,9 @@
[package]
name = "pluginapi"
name = "nsis-plugin-api"
version = "0.0.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
windows-sys = { workspace = true }
nsis-fn = { path = "../nsis-fn" }

View File

@@ -0,0 +1,254 @@
#![no_std]
#![allow(unused)]
#![allow(nonstandard_style)]
extern crate alloc;
use core::{
ffi::{c_int, c_void},
fmt::Display,
iter,
mem::{size_of, size_of_val},
};
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::{
alloc::{GlobalAlloc, Layout},
vec::Vec,
};
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;
#[repr(C)]
#[derive(Debug)]
pub struct stack_t {
pub next: *mut stack_t,
pub text: [wchar_t; 1],
}
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;
G_VARIABLES = variables;
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];
#[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()) };
}
}
/// 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);
}
let n = size_of::<stack_t>() + G_STRINGSIZE as usize * 2;
let th = GlobalAlloc(GPTR, n) as *mut stack_t;
lstrcpyW((*th).text.as_ptr() as _, bytes.as_ptr());
(*th).next = *G_STACKTOP;
*G_STACKTOP = th;
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_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<Vec<u16>, Error> {
if G_STACKTOP.is_null() || (*G_STACKTOP).is_null() {
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)
}
/// 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<String, Error> {
let bytes = pop()?;
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<i32, Error> {
let str = popstr()?;
str.parse().map_err(|_| Error::ParseIntError)
}
pub fn encode_utf16(str: &str) -> Vec<u16> {
str.encode_utf16()
.chain(iter::once(0))
.collect::<Vec<u16>>()
}
pub fn decode_utf16_lossy(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]
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 `mem*` extern functions
#[macro_export]
macro_rules! nsis_plugin {
() => {
#[no_mangle]
extern "system" fn DllMain(
dll_module: ::windows_sys::Win32::Foundation::HINSTANCE,
call_reason: u32,
_: *mut (),
) -> bool {
true
}
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
unsafe { ::windows_sys::Win32::System::Threading::ExitProcess(u32::MAX) }
}
#[no_mangle]
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) = *src.offset(i);
i += 1;
}
return dest;
}
#[no_mangle]
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);
let b = *s2.offset(i);
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: isize) -> *mut u8 {
let mut i = 0;
while i < n {
*s.offset(i) = c as u8;
i += 1;
}
return s;
}
};
}

View File

@@ -6,8 +6,11 @@ edition = { workspace = true }
license = { workspace = true }
[lib]
crate-type = [ "rlib", "cdylib" ]
crate-type = ["cdylib"]
[features]
test = []
[dependencies]
pluginapi = { workspace = true }
nsis-plugin-api = { workspace = true }
windows-sys = { workspace = true }

View File

@@ -0,0 +1,5 @@
fn main() {
if std::env::var("CARGO_FEATURE_TEST").as_deref() != Ok("1") {
println!("cargo::rustc-link-arg=/ENTRY:DllMain")
}
}

View File

@@ -1,9 +1,14 @@
use std::{ffi::c_void, mem, ptr};
#![no_std]
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 +16,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);
let name = popstring().unwrap();
#[nsis_fn]
fn FindProcess() -> Result<(), Error> {
let name = popstr()?;
if !get_processes(&name).is_empty() {
pushint(0);
push(ZERO)
} else {
pushint(1);
push(ONE)
}
}
@@ -45,33 +45,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);
let name = popstring().unwrap();
#[nsis_fn]
fn FindProcessCurrentUser() -> Result<(), Error> {
let name = popstr()?;
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);
push(ZERO)
} else {
pushint(1);
push(ONE)
}
// Fall back to perMachine checks if we can't get current user id
} else if processes.is_empty() {
pushint(1);
push(ONE)
} else {
pushint(0);
push(ZERO)
}
}
@@ -80,23 +73,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);
let name = popstring().unwrap();
#[nsis_fn]
fn KillProcess() -> Result<(), Error> {
let name = popstr()?;
let processes = get_processes(&name);
if !processes.is_empty() && processes.into_iter().map(kill).all(|b| b) {
pushint(0);
push(ZERO)
} else {
pushint(1);
push(ONE)
}
}
@@ -105,25 +91,17 @@ 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);
let name = popstring().unwrap();
#[nsis_fn]
fn KillProcessCurrentUser() -> Result<(), Error> {
let name = popstr()?;
let processes = get_processes(&name);
if processes.is_empty() {
pushint(1);
return;
return push(ONE);
}
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 +112,9 @@ pub unsafe extern "C" fn KillProcessCurrentUser(
};
if success {
pushint(0)
push(ZERO)
} else {
pushint(1)
push(ONE)
}
}
@@ -204,7 +182,7 @@ unsafe fn get_sid(pid: u32) -> Option<*mut c_void> {
}
fn get_processes(name: &str) -> Vec<u32> {
let current_pid = std::process::id();
let current_pid = unsafe { GetCurrentProcessId() };
let mut processes = Vec::new();
unsafe {
@@ -218,11 +196,7 @@ fn get_processes(name: &str) -> Vec<u32> {
if Process32FirstW(handle, &mut process) != 0 {
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()
&& decode_utf16_lossy(&process.szExeFile).to_lowercase() == name.to_lowercase()
{
processes.push(process.th32ProcessID);
}
@@ -242,14 +216,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));

View File

@@ -6,9 +6,12 @@ edition = { workspace = true }
license = { workspace = true }
[lib]
crate-type = ["rlib", "cdylib"]
crate-type = ["cdylib"]
[features]
test = []
[dependencies]
semver = "1.0"
pluginapi = { workspace = true }
semver = { version = "1.0", default-features = false }
nsis-plugin-api = { workspace = true }
windows-sys = { workspace = true }

View File

@@ -0,0 +1,5 @@
fn main() {
if std::env::var("CARGO_FEATURE_TEST").as_deref() != Ok("1") {
println!("cargo::rustc-link-arg=/ENTRY:DllMain")
}
}

View File

@@ -1,33 +1,37 @@
use std::{cmp::Ordering, str::FromStr};
#![no_std]
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.
///
/// 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.
#[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);
/// This function always expects 2 strings on the stack ($v1, $v2) and will panic otherwise.
#[nsis_fn]
fn SemverCompare() -> Result<(), Error> {
let v1 = popstr()?;
let v2 = popstr()?;
let v1 = popstring().unwrap();
let v2 = popstring().unwrap();
match compare(&v1, &v2) {
-1 => push(NEGATIVE_ONE)?,
0 => push(ZERO)?,
1 => push(ONE)?,
_ => unreachable!(),
}
let ret = semver_compare(&v1, &v2);
pushint(ret);
Ok(())
}
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 +68,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);
}
}
}

View File

@@ -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"]
[features]
test = []
[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 }

View File

@@ -0,0 +1,44 @@
fn main() {
combine_plugins_and_write_to_out_dir();
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
/// 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");
let mut file = std::fs::File::options()
.truncate(true)
.write(true)
.create(true)
.open(path)
.unwrap();
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("nsis_plugin!();")
|| l.contains("use nsis_plugin_api::*;"))
})
.take_while(|l| !l.contains("mod tests {"))
.collect::<Vec<&str>>();
// 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();
}
}

View File

@@ -1,3 +1,7 @@
pub use nsis_download::*;
pub use nsis_process::*;
pub use nsis_semvercompare::*;
#![no_std]
use nsis_plugin_api::*;
nsis_plugin!();
include!(concat!(env!("OUT_DIR"), "/combined_libs.rs"));

View File

@@ -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<OsStr>) {
if G_STACKTOP.is_null() {
return;
}
let string_wide = encode_wide(s);
let th: *mut stack_t = GlobalAlloc(
GPTR,
size_of::<stack_t>() + 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<String, Error> {
if G_STACKTOP.is_null() || (*G_STACKTOP).is_null() {
return Err(Error::InvalidStackError);
}
let mut string_wide: Vec<u16> = 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<OsStr>) -> Vec<u16> {
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)
}

View File

@@ -1,19 +1,11 @@
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"
!include "MUI2.nsh"
@@ -23,14 +15,11 @@ Unicode true
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