mirror of
https://github.com/tauri-apps/nsis-tauri-utils.git
synced 2026-01-31 00:45:23 +01:00
refactor!: no_std & no static msvcrt & remove download plugin (#26)
This commit is contained in:
@@ -1,5 +1,2 @@
|
||||
[build]
|
||||
target = "i686-pc-windows-msvc"
|
||||
|
||||
[target.i686-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
@@ -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
7
.changes/no_std.md
Normal 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.
|
||||
5
.github/workflows/audit.yml
vendored
5
.github/workflows/audit.yml
vendored
@@ -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 }}
|
||||
2
.github/workflows/change-status-on-pr.yml
vendored
2
.github/workflows/change-status-on-pr.yml
vendored
@@ -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
|
||||
|
||||
30
.github/workflows/clippy-fmt.yml
vendored
30
.github/workflows/clippy-fmt.yml
vendored
@@ -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
|
||||
@@ -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: |
|
||||
|
||||
15
.github/workflows/test.yml
vendored
15
.github/workflows/test.yml
vendored
@@ -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
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -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
|
||||
|
||||
13
README.md
13
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
|
||||
Apache-2.0/MIT
|
||||
|
||||
@@ -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
|
||||
@@ -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 }
|
||||
@@ -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
13
crates/nsis-fn/Cargo.toml
Normal 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
51
crates/nsis-fn/src/lib.rs
Normal 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()
|
||||
}
|
||||
@@ -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" }
|
||||
254
crates/nsis-plugin-api/src/lib.rs
Normal file
254
crates/nsis-plugin-api/src/lib.rs
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
5
crates/nsis-process/build.rs
Normal file
5
crates/nsis-process/build.rs
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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 }
|
||||
|
||||
5
crates/nsis-semvercompare/build.rs
Normal file
5
crates/nsis-semvercompare/build.rs
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
44
crates/nsis-tauri-utils/build.rs
Normal file
44
crates/nsis-tauri-utils/build.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
19
demo.nsi
19
demo.nsi
@@ -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
|
||||
Reference in New Issue
Block a user