1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#![windows_subsystem = "windows"]
use windows_sys::Win32::System::LibraryLoader::GetModuleHandleW;
use windows_sys::Win32::System::Com::{CoInitializeEx, COINIT_APARTMENTTHREADED};
use windows_sys::Win32::UI::WindowsAndMessaging::{
MessageBoxW, MB_ICONERROR, MB_OK, SW_SHOW, FindWindowW, SendMessageW,
WM_COPYDATA,
};
use windows_sys::Win32::System::DataExchange::COPYDATASTRUCT;
use windows_sys::Win32::UI::Shell::{IsUserAnAdmin, ShellExecuteW};
use windows_sys::Win32::System::LibraryLoader::GetModuleFileNameW;
use std::ptr;
use std::sync::OnceLock;
pub mod ui;
pub mod engine;
pub mod config;
pub mod registry;
pub mod utils;
pub mod updater;
mod logger;
pub mod com;
use crate::engine::wof::WofAlgorithm;
use crate::ui::state::BatchAction;
use crate::utils::to_wstring;
/// Startup item passed via command line arguments
#[derive(Clone, Debug)]
pub struct StartupItem {
pub path: String,
pub algorithm: WofAlgorithm,
pub action: BatchAction,
}
/// Global storage for startup items from CLI arguments
static STARTUP_ITEMS: OnceLock<Vec<StartupItem>> = OnceLock::new();
/// Get startup items parsed from command line (if any)
pub fn get_startup_items() -> &'static [StartupItem] {
STARTUP_ITEMS.get().map(|v| v.as_slice()).unwrap_or(&[])
}
/// Parse command line arguments
fn parse_cli_args() -> Vec<StartupItem> {
let args: Vec<String> = std::env::args().collect();
let mut items = Vec::new();
let mut i = 1; // Skip executable name
while i < args.len() {
if args[i] == "--path" && i + 1 < args.len() {
let path = args[i + 1].clone();
i += 2;
// Look for --algo or --action
let mut algorithm = WofAlgorithm::Xpress8K; // Default
let mut action = BatchAction::Compress; // Default
while i < args.len() {
if args[i] == "--algo" && i + 1 < args.len() {
algorithm = match args[i + 1].to_lowercase().as_str() {
"xpress4k" => WofAlgorithm::Xpress4K,
"xpress8k" => WofAlgorithm::Xpress8K,
"xpress16k" => WofAlgorithm::Xpress16K,
"lzx" => WofAlgorithm::Lzx,
_ => WofAlgorithm::Xpress8K,
};
i += 2;
} else if args[i] == "--action" && i + 1 < args.len() {
action = match args[i + 1].to_lowercase().as_str() {
"decompress" => BatchAction::Decompress,
_ => BatchAction::Compress,
};
i += 2;
} else if args[i] == "--path" {
// Next item starts, don't consume
break;
} else {
i += 1;
}
}
items.push(StartupItem { path, algorithm, action });
} else {
i += 1;
}
}
items
}
// Helper function to check for admin privileges
fn is_admin() -> bool {
unsafe { IsUserAnAdmin() != 0 }
}
fn main() {
// Initialize Theme System early
crate::ui::theme::init();
crate::ui::theme::set_preferred_app_mode(true);
// 0. Single Instance Check (Before Admin Check)
unsafe {
let class_name = w!("CompactRS_Class");
let hwnd_existing = FindWindowW(class_name.as_ptr(), std::ptr::null());
if hwnd_existing != std::ptr::null_mut() {
let items = parse_cli_args();
if !items.is_empty() {
for item in items {
let algo_str = match item.algorithm {
WofAlgorithm::Xpress4K => "xpress4k",
WofAlgorithm::Xpress8K => "xpress8k",
WofAlgorithm::Xpress16K => "xpress16k",
WofAlgorithm::Lzx => "lzx",
};
let action_str = match item.action {
BatchAction::Compress => "compress",
BatchAction::Decompress => "decompress",
};
// Format: PATH|ALGO|ACTION
let payload = format!("{}|{}|{}", item.path, algo_str, action_str);
let payload_w = to_wstring(&payload);
let cds = COPYDATASTRUCT {
dwData: 0xB00B,
cbData: (payload_w.len() * 2) as u32,
lpData: payload_w.as_ptr() as *mut _,
};
SendMessageW(hwnd_existing, WM_COPYDATA, 0, &cds as *const _ as isize);
}
}
std::process::exit(0);
}
}
// 1. Runtime Admin Check
if !is_admin() {
unsafe {
// Attempt to relaunch as administrator
let mut filename = [0u16; 32768]; // MAX_PATH is 260 but wide paths can be longer, using safe buffer
let len = GetModuleFileNameW(std::ptr::null_mut(), filename.as_mut_ptr(), filename.len() as u32);
if len > 0 {
let operation = w!("runas");
// Collect existing arguments and quote them if necessary to preserve spaces during elevation
let args: Vec<String> = std::env::args().skip(1).collect();
let args_str = args.iter()
.map(|arg| {
if arg.contains(' ') || arg.contains('\t') || arg.is_empty() {
format!("\"{}\"", arg)
} else {
arg.clone()
}
})
.collect::<Vec<String>>()
.join(" ");
let args_wide = to_wstring(&args_str);
let res = ShellExecuteW(
std::ptr::null_mut(),
operation.as_ptr(),
filename.as_ptr(),
if args.is_empty() { ptr::null() } else { args_wide.as_ptr() },
ptr::null(),
SW_SHOW
);
// If ShellExecuteW returns > 32, it succeeded
if res as isize > 32 {
std::process::exit(0); // Exit this non-admin instance immediately
}
}
// If elevation failed (user declined UAC, etc.), show error
let title = w!("Privilege Error");
let msg = w!("CompactRS requires Administrator privileges to perform compression operations.\n\nFailed to elevate privileges. Please restart as Administrator.");
MessageBoxW(
std::ptr::null_mut(),
msg.as_ptr(),
title.as_ptr(),
MB_ICONERROR | MB_OK
);
}
std::process::exit(1);
}
// Parse CLI arguments before GUI initialization
let startup_items = parse_cli_args();
let _ = STARTUP_ITEMS.set(startup_items);
// Cleanup old executable if it exists (from self-update)
if let Ok(exe) = std::env::current_exe() {
let old_exe = exe.with_extension("old");
if old_exe.exists() {
// We can just try to delete it. If it fails (still locked?), we ignore.
// It will be cleaned up next time.
let _ = std::fs::remove_file(old_exe);
}
}
unsafe {
// Initialize COM for IFileOpenDialog
// Ignore result, it might already be initialized
// Note: windows-sys defines COINIT_APARTMENTTHREADED as i32 (0x2), CoInitializeEx expects u32
let _ = CoInitializeEx(ptr::null_mut(), COINIT_APARTMENTTHREADED as u32);
let instance = GetModuleHandleW(ptr::null());
// We'll update create_main_window to accept isize (HINSTANCE)
// We'll update create_main_window to accept isize (HINSTANCE)
let hwnd_main = match ui::window::create_main_window(instance) {
Ok(h) => h,
Err(e) => {
let msg = to_wstring(&("Failed to create main window: ".to_string() + &e.to_string()));
MessageBoxW(std::ptr::null_mut(), msg.as_ptr(), w!("Error").as_ptr(), MB_ICONERROR | MB_OK);
std::process::exit(1);
}
};
// Message Loop
// We now use the framework's run_message_loop which supports IsDialogMessageW
ui::framework::run_message_loop(hwnd_main);
// Clean up COM
windows_sys::Win32::System::Com::CoUninitialize();
// Force process exit to ensure no background threads start
std::process::exit(0);
}
}