Compare commits
5 Commits
64de0ade31
...
50409fdd83
| Author | SHA1 | Date |
|---|---|---|
|
|
50409fdd83 | 6 days ago |
|
|
202e2c160f | 6 days ago |
|
|
adc456b8f6 | 6 days ago |
|
|
821435c8a3 | 6 days ago |
|
|
f8a1dde721 | 6 days ago |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,35 @@
|
|||||||
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
members = [
|
||||||
|
"crates/clean_flash_common",
|
||||||
|
"crates/clean_flash_ui",
|
||||||
|
"crates/clean_flash_installer",
|
||||||
|
"crates/clean_flash_uninstaller",
|
||||||
|
]
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
ab_glyph = "0.2"
|
||||||
|
sevenz-rust2 = { version = "0.20", default-feaures = false, features = ["compress", "util"] }
|
||||||
|
minifb = "0.28"
|
||||||
|
windows-sys = { version = "0.61", features = [
|
||||||
|
"Win32_Foundation",
|
||||||
|
"Win32_Networking_WinHttp",
|
||||||
|
"Win32_Security",
|
||||||
|
"Win32_Security_Authorization",
|
||||||
|
"Win32_System_LibraryLoader",
|
||||||
|
"Win32_System_RestartManager",
|
||||||
|
"Win32_System_Threading",
|
||||||
|
"Win32_System_ProcessStatus",
|
||||||
|
"Win32_Storage_FileSystem",
|
||||||
|
"Win32_System_Registry",
|
||||||
|
"Win32_System_SystemInformation",
|
||||||
|
"Win32_UI_HiDpi",
|
||||||
|
"Win32_UI_Shell",
|
||||||
|
"Win32_UI_WindowsAndMessaging",
|
||||||
|
] }
|
||||||
|
|
||||||
|
[profile."release-lto"]
|
||||||
|
inherits = "release"
|
||||||
|
codegen-units = 1
|
||||||
|
lto = "fat"
|
||||||
|
strip = true
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "clean_flash_common"
|
||||||
|
version = "34.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
windows-sys = { workspace = true }
|
||||||
@ -0,0 +1,267 @@
|
|||||||
|
use crate::uninstaller;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
/// Attempt to delete a single file, retrying with escalating measures if needed.
|
||||||
|
pub fn delete_file(path: &Path) {
|
||||||
|
if !path.exists() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unregister ActiveX .ocx files before deletion.
|
||||||
|
if let Some(ext) = path.extension() {
|
||||||
|
if ext.eq_ignore_ascii_case("ocx") {
|
||||||
|
let _ = uninstaller::unregister_activex(&path.to_string_lossy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First attempt: clear read-only and delete.
|
||||||
|
if try_clear_readonly_and_delete(path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry loop with ownership acquisition.
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
for _ in 0..10 {
|
||||||
|
if try_take_ownership_and_delete(path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last resort: kill any processes using the file, then delete.
|
||||||
|
kill_locking_processes(path);
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
let _ = fs::remove_file(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively delete all files (optionally matching `filename`) under `base_dir`.
|
||||||
|
pub fn recursive_delete(base_dir: &Path, filename: Option<&str>) {
|
||||||
|
if !base_dir.exists() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let entries: Vec<_> = match fs::read_dir(base_dir) {
|
||||||
|
Ok(rd) => rd.filter_map(|e| e.ok()).collect(),
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
if path.is_dir() {
|
||||||
|
recursive_delete(&path, filename);
|
||||||
|
} else if path.is_file() {
|
||||||
|
// Sanity check: path must start with the original base_dir.
|
||||||
|
if !path.starts_with(base_dir) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let should_delete = match filename {
|
||||||
|
Some(name) => path
|
||||||
|
.file_name()
|
||||||
|
.map(|f| f == name)
|
||||||
|
.unwrap_or(false),
|
||||||
|
None => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if should_delete {
|
||||||
|
delete_file(&path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete all files in a folder, then try to remove the folder itself.
|
||||||
|
pub fn wipe_folder(path: &Path) {
|
||||||
|
if !path.exists() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recursive_delete(path, None);
|
||||||
|
|
||||||
|
// If folder is now empty, remove it.
|
||||||
|
if is_dir_empty(path) {
|
||||||
|
if fs::remove_dir(path).is_err() {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
kill_locking_processes(path);
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
let _ = fs::remove_dir(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_clear_readonly_and_delete(path: &Path) -> bool {
|
||||||
|
if let Ok(meta) = fs::metadata(path) {
|
||||||
|
let mut perms = meta.permissions();
|
||||||
|
#[allow(clippy::permissions_set_readonly_false)]
|
||||||
|
perms.set_readonly(false);
|
||||||
|
let _ = fs::set_permissions(path, perms);
|
||||||
|
}
|
||||||
|
fs::remove_file(path).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn try_take_ownership_and_delete(path: &Path) -> bool {
|
||||||
|
use windows_sys::Win32::Foundation::{CloseHandle, LocalFree};
|
||||||
|
use windows_sys::Win32::Security::{
|
||||||
|
GetTokenInformation, TokenUser, DACL_SECURITY_INFORMATION,
|
||||||
|
OWNER_SECURITY_INFORMATION, TOKEN_QUERY, TOKEN_USER,
|
||||||
|
};
|
||||||
|
use windows_sys::Win32::Security::Authorization::{
|
||||||
|
SetEntriesInAclW, SetNamedSecurityInfoW, EXPLICIT_ACCESS_W,
|
||||||
|
SE_FILE_OBJECT, SET_ACCESS, TRUSTEE_IS_SID, TRUSTEE_IS_USER, TRUSTEE_W,
|
||||||
|
};
|
||||||
|
use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
|
||||||
|
|
||||||
|
// Ensure SeTakeOwnershipPrivilege is enabled (idempotent).
|
||||||
|
crate::winapi_helpers::allow_modifications();
|
||||||
|
|
||||||
|
let path_wide: Vec<u16> = path
|
||||||
|
.to_string_lossy()
|
||||||
|
.encode_utf16()
|
||||||
|
.chain(std::iter::once(0))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut token = std::ptr::null_mut();
|
||||||
|
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) == 0 {
|
||||||
|
return try_clear_readonly_and_delete(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the current user's SID from the process token.
|
||||||
|
let mut buf = vec![0u8; 512];
|
||||||
|
let mut returned = 0u32;
|
||||||
|
let ok = GetTokenInformation(
|
||||||
|
token,
|
||||||
|
TokenUser,
|
||||||
|
buf.as_mut_ptr() as *mut _,
|
||||||
|
buf.len() as u32,
|
||||||
|
&mut returned,
|
||||||
|
);
|
||||||
|
CloseHandle(token);
|
||||||
|
|
||||||
|
if ok == 0 {
|
||||||
|
return try_clear_readonly_and_delete(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let token_user = &*(buf.as_ptr() as *const TOKEN_USER);
|
||||||
|
let sid = token_user.User.Sid;
|
||||||
|
|
||||||
|
// Transfer ownership of the file to the current user.
|
||||||
|
SetNamedSecurityInfoW(
|
||||||
|
path_wide.as_ptr() as *mut _,
|
||||||
|
SE_FILE_OBJECT,
|
||||||
|
OWNER_SECURITY_INFORMATION,
|
||||||
|
sid,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build a new DACL that grants FullControl to the current user.
|
||||||
|
let mut ea = EXPLICIT_ACCESS_W {
|
||||||
|
grfAccessPermissions: 0x001F_01FF, // FILE_ALL_ACCESS
|
||||||
|
grfAccessMode: SET_ACCESS,
|
||||||
|
grfInheritance: 0, // NO_INHERITANCE
|
||||||
|
Trustee: TRUSTEE_W {
|
||||||
|
pMultipleTrustee: std::ptr::null_mut(),
|
||||||
|
MultipleTrusteeOperation: 0, // NO_MULTIPLE_TRUSTEE
|
||||||
|
TrusteeForm: TRUSTEE_IS_SID,
|
||||||
|
TrusteeType: TRUSTEE_IS_USER,
|
||||||
|
ptstrName: sid as *mut u16,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let mut new_dacl: *mut windows_sys::Win32::Security::ACL = std::ptr::null_mut();
|
||||||
|
SetEntriesInAclW(1, &mut ea, std::ptr::null_mut(), &mut new_dacl);
|
||||||
|
|
||||||
|
if !new_dacl.is_null() {
|
||||||
|
SetNamedSecurityInfoW(
|
||||||
|
path_wide.as_ptr() as *mut _,
|
||||||
|
SE_FILE_OBJECT,
|
||||||
|
DACL_SECURITY_INFORMATION,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
new_dacl,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
);
|
||||||
|
LocalFree(new_dacl as *mut _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try_clear_readonly_and_delete(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn kill_locking_processes(path: &Path) {
|
||||||
|
use windows_sys::Win32::Foundation::CloseHandle;
|
||||||
|
use windows_sys::Win32::System::RestartManager::{
|
||||||
|
RmEndSession, RmGetList, RmRegisterResources, RmStartSession, RM_PROCESS_INFO,
|
||||||
|
};
|
||||||
|
use windows_sys::Win32::System::Threading::{
|
||||||
|
OpenProcess, TerminateProcess, WaitForSingleObject, PROCESS_TERMINATE,
|
||||||
|
};
|
||||||
|
|
||||||
|
let path_wide: Vec<u16> = path
|
||||||
|
.to_string_lossy()
|
||||||
|
.encode_utf16()
|
||||||
|
.chain(std::iter::once(0))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut session: u32 = 0;
|
||||||
|
// CCH_RM_SESSION_KEY = 32 chars; +1 for null terminator.
|
||||||
|
let mut session_key = [0u16; 33];
|
||||||
|
if RmStartSession(&mut session, 0, session_key.as_mut_ptr()) != 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_ptr = path_wide.as_ptr();
|
||||||
|
let files = [file_ptr];
|
||||||
|
RmRegisterResources(
|
||||||
|
session,
|
||||||
|
1,
|
||||||
|
files.as_ptr(),
|
||||||
|
0,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
0,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut n_needed: u32 = 0;
|
||||||
|
let mut n_info: u32 = 10;
|
||||||
|
let mut procs: [RM_PROCESS_INFO; 10] = std::mem::zeroed();
|
||||||
|
let mut reboot_reasons: u32 = 0;
|
||||||
|
RmGetList(
|
||||||
|
session,
|
||||||
|
&mut n_needed,
|
||||||
|
&mut n_info,
|
||||||
|
procs.as_mut_ptr(),
|
||||||
|
&mut reboot_reasons,
|
||||||
|
);
|
||||||
|
|
||||||
|
for proc_info in procs.iter().take(n_info as usize) {
|
||||||
|
let pid = proc_info.Process.dwProcessId;
|
||||||
|
let handle = OpenProcess(PROCESS_TERMINATE, 0, pid);
|
||||||
|
if !handle.is_null() {
|
||||||
|
TerminateProcess(handle, 1);
|
||||||
|
WaitForSingleObject(handle, 5000);
|
||||||
|
CloseHandle(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RmEndSession(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_dir_empty(path: &Path) -> bool {
|
||||||
|
fs::read_dir(path)
|
||||||
|
.map(|mut rd| rd.next().is_none())
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
pub mod file_util;
|
||||||
|
pub mod native_host;
|
||||||
|
pub mod process_utils;
|
||||||
|
pub mod redirection;
|
||||||
|
pub mod registry;
|
||||||
|
pub mod resources;
|
||||||
|
pub mod system_info;
|
||||||
|
pub mod uninstaller;
|
||||||
|
pub mod update_checker;
|
||||||
|
pub mod winapi_helpers;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// Progress callback trait, analogous to C# IProgressForm.
|
||||||
|
pub trait ProgressCallback: Send {
|
||||||
|
fn update_progress_label(&self, text: &str, tick: bool);
|
||||||
|
fn tick_progress(&self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Install/uninstall error type.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InstallError {
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstallError {
|
||||||
|
pub fn new(message: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
message: message.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for InstallError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for InstallError {}
|
||||||
|
|
||||||
|
/// Result of running an external process.
|
||||||
|
pub struct ExitedProcess {
|
||||||
|
pub exit_code: i32,
|
||||||
|
pub output: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExitedProcess {
|
||||||
|
pub fn is_successful(&self) -> bool {
|
||||||
|
self.exit_code == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,444 @@
|
|||||||
|
//! Install and uninstall the native messaging host for modern browsers.
|
||||||
|
//!
|
||||||
|
//! The native messaging host allows the Clean Flash Player browser extension
|
||||||
|
//! to communicate with the local flash-player-host binary via stdio.
|
||||||
|
|
||||||
|
use crate::{InstallError, ProgressCallback};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
const MANIFEST_NAME: &str = "org.cleanflash.flash_player";
|
||||||
|
const FIREFOX_MANIFEST_FILENAME: &str = "org.cleanflash.flash_player.firefox.json";
|
||||||
|
const CHROME_MANIFEST_FILENAME: &str = "org.cleanflash.flash_player.chrome.json";
|
||||||
|
const FIREFOX_ALLOWED_EXTENSION: &str = "flash-player@cleanflash.org";
|
||||||
|
const ALLOWED_ORIGIN: &str = "chrome-extension://dcikaadaeajidejkoekdflmfdgeoldcb/";
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
const HOST_BINARY_NAME: &str = "flash-player-host.exe";
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
const HOST_BINARY_NAME: &str = "flash-player-host";
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum BrowserKind {
|
||||||
|
Firefox,
|
||||||
|
ChromeLike,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
struct WindowsBrowser {
|
||||||
|
detect_keys: &'static [&'static str],
|
||||||
|
native_messaging_reg_path: &'static str,
|
||||||
|
kind: BrowserKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Browser registry key paths on Windows (HKCU/HKLM) for native messaging hosts.
|
||||||
|
#[cfg(windows)]
|
||||||
|
const WINDOWS_BROWSERS: &[WindowsBrowser] = &[
|
||||||
|
WindowsBrowser {
|
||||||
|
detect_keys: &[r"SOFTWARE\Google\Chrome"],
|
||||||
|
native_messaging_reg_path: r"SOFTWARE\Google\Chrome\NativeMessagingHosts",
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
WindowsBrowser {
|
||||||
|
detect_keys: &[r"SOFTWARE\Microsoft\Edge"],
|
||||||
|
native_messaging_reg_path: r"SOFTWARE\Microsoft\Edge\NativeMessagingHosts",
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
WindowsBrowser {
|
||||||
|
detect_keys: &[r"SOFTWARE\BraveSoftware\Brave-Browser"],
|
||||||
|
native_messaging_reg_path: r"SOFTWARE\BraveSoftware\Brave-Browser\NativeMessagingHosts",
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
WindowsBrowser {
|
||||||
|
detect_keys: &[r"SOFTWARE\Vivaldi"],
|
||||||
|
native_messaging_reg_path: r"SOFTWARE\Vivaldi\NativeMessagingHosts",
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
WindowsBrowser {
|
||||||
|
detect_keys: &[r"SOFTWARE\Opera Software"],
|
||||||
|
native_messaging_reg_path: r"SOFTWARE\Opera Software\NativeMessagingHosts",
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
WindowsBrowser {
|
||||||
|
detect_keys: &[r"SOFTWARE\Chromium"],
|
||||||
|
native_messaging_reg_path: r"SOFTWARE\Chromium\NativeMessagingHosts",
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
WindowsBrowser {
|
||||||
|
detect_keys: &[r"SOFTWARE\The Browser Company\Arc"],
|
||||||
|
native_messaging_reg_path: r"SOFTWARE\The Browser Company\Arc\NativeMessagingHosts",
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
WindowsBrowser {
|
||||||
|
detect_keys: &[r"SOFTWARE\Mozilla\Mozilla Firefox", r"SOFTWARE\Mozilla"],
|
||||||
|
native_messaging_reg_path: r"SOFTWARE\Mozilla\NativeMessagingHosts",
|
||||||
|
kind: BrowserKind::Firefox,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
struct BrowserManifestTarget {
|
||||||
|
detect_path: PathBuf,
|
||||||
|
manifest_dir: PathBuf,
|
||||||
|
kind: BrowserKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the JSON manifest content for the native messaging host.
|
||||||
|
fn build_manifest_json(host_path: &Path, kind: BrowserKind) -> String {
|
||||||
|
// Escape backslashes in the path for JSON.
|
||||||
|
let path_str = host_path.to_string_lossy().replace('\\', "\\\\");
|
||||||
|
match kind {
|
||||||
|
BrowserKind::Firefox => format!(
|
||||||
|
"{{\n \"name\": \"{}\",\n \"description\": \"Flash Player Native Messaging Host\",\n \"path\": \"{}\",\n \"type\": \"stdio\",\n \"allowed_extensions\": [\"{}\"]\n}}\n",
|
||||||
|
MANIFEST_NAME, path_str, FIREFOX_ALLOWED_EXTENSION,
|
||||||
|
),
|
||||||
|
BrowserKind::ChromeLike => format!(
|
||||||
|
"{{\n \"name\": \"{}\",\n \"description\": \"Flash Player Native Messaging Host\",\n \"path\": \"{}\",\n \"type\": \"stdio\",\n \"allowed_origins\": [\"{}\"]\n}}\n",
|
||||||
|
MANIFEST_NAME, path_str, ALLOWED_ORIGIN,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the path where the native host exe should be installed on Windows.
|
||||||
|
/// On a 64-bit system it goes into the System32\Macromed\Flash folder (the 64-bit one).
|
||||||
|
/// On a 32-bit system it goes into the SysWOW64 (or System32) Macromed\Flash folder.
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn get_native_host_install_dir() -> PathBuf {
|
||||||
|
crate::system_info::with_system_info(|si| {
|
||||||
|
if si.is_64bit {
|
||||||
|
si.flash64_path.clone()
|
||||||
|
} else {
|
||||||
|
si.flash32_path.clone()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
pub fn get_native_host_install_dir() -> PathBuf {
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
PathBuf::from("/Library/Application Support/Clean Flash")
|
||||||
|
} else {
|
||||||
|
// Linux
|
||||||
|
if let Ok(home) = std::env::var("HOME") {
|
||||||
|
PathBuf::from(home).join(".cleanflash")
|
||||||
|
} else {
|
||||||
|
PathBuf::from("/tmp/.cleanflash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the full path to the installed native host executable.
|
||||||
|
pub fn get_native_host_exe_path() -> PathBuf {
|
||||||
|
get_native_host_install_dir().join(HOST_BINARY_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn manifest_filename_for_kind(kind: BrowserKind) -> &'static str {
|
||||||
|
match kind {
|
||||||
|
BrowserKind::Firefox => FIREFOX_MANIFEST_FILENAME,
|
||||||
|
BrowserKind::ChromeLike => CHROME_MANIFEST_FILENAME,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn get_non_windows_browser_targets(home: &Path) -> Vec<BrowserManifestTarget> {
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
vec![
|
||||||
|
BrowserManifestTarget {
|
||||||
|
detect_path: home.join("Library/Application Support/Google/Chrome"),
|
||||||
|
manifest_dir: home.join("Library/Application Support/Google/Chrome/NativeMessagingHosts"),
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
BrowserManifestTarget {
|
||||||
|
detect_path: home.join("Library/Application Support/Microsoft Edge"),
|
||||||
|
manifest_dir: home.join("Library/Application Support/Microsoft Edge/NativeMessagingHosts"),
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
BrowserManifestTarget {
|
||||||
|
detect_path: home.join("Library/Application Support/BraveSoftware/Brave-Browser"),
|
||||||
|
manifest_dir: home.join("Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts"),
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
BrowserManifestTarget {
|
||||||
|
detect_path: home.join("Library/Application Support/Vivaldi"),
|
||||||
|
manifest_dir: home.join("Library/Application Support/Vivaldi/NativeMessagingHosts"),
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
BrowserManifestTarget {
|
||||||
|
detect_path: home.join("Library/Application Support/Chromium"),
|
||||||
|
manifest_dir: home.join("Library/Application Support/Chromium/NativeMessagingHosts"),
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
BrowserManifestTarget {
|
||||||
|
detect_path: home.join("Library/Application Support/Arc"),
|
||||||
|
manifest_dir: home.join("Library/Application Support/Arc/NativeMessagingHosts"),
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
BrowserManifestTarget {
|
||||||
|
detect_path: home.join("Library/Application Support/Mozilla"),
|
||||||
|
manifest_dir: home.join("Library/Application Support/Mozilla/NativeMessagingHosts"),
|
||||||
|
kind: BrowserKind::Firefox,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
BrowserManifestTarget {
|
||||||
|
detect_path: home.join(".config/google-chrome"),
|
||||||
|
manifest_dir: home.join(".config/google-chrome/NativeMessagingHosts"),
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
BrowserManifestTarget {
|
||||||
|
detect_path: home.join(".config/microsoft-edge"),
|
||||||
|
manifest_dir: home.join(".config/microsoft-edge/NativeMessagingHosts"),
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
BrowserManifestTarget {
|
||||||
|
detect_path: home.join(".config/BraveSoftware/Brave-Browser"),
|
||||||
|
manifest_dir: home.join(".config/BraveSoftware/Brave-Browser/NativeMessagingHosts"),
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
BrowserManifestTarget {
|
||||||
|
detect_path: home.join(".config/vivaldi"),
|
||||||
|
manifest_dir: home.join(".config/vivaldi/NativeMessagingHosts"),
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
BrowserManifestTarget {
|
||||||
|
detect_path: home.join(".config/chromium"),
|
||||||
|
manifest_dir: home.join(".config/chromium/NativeMessagingHosts"),
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
BrowserManifestTarget {
|
||||||
|
detect_path: home.join(".config/arc"),
|
||||||
|
manifest_dir: home.join(".config/arc/NativeMessagingHosts"),
|
||||||
|
kind: BrowserKind::ChromeLike,
|
||||||
|
},
|
||||||
|
BrowserManifestTarget {
|
||||||
|
detect_path: home.join(".mozilla"),
|
||||||
|
manifest_dir: home.join(".mozilla/native-messaging-hosts"),
|
||||||
|
kind: BrowserKind::Firefox,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn set_windows_manifest_registry_value(nmh_base_key: &str, manifest_path: &str) {
|
||||||
|
use windows_sys::Win32::System::Registry::{
|
||||||
|
RegCloseKey, RegCreateKeyExW, RegSetValueExW, HKEY_CURRENT_USER,
|
||||||
|
KEY_WOW64_64KEY, KEY_WRITE, REG_OPTION_NON_VOLATILE, REG_SZ,
|
||||||
|
};
|
||||||
|
|
||||||
|
let reg_key = format!("{}\\{}", nmh_base_key, MANIFEST_NAME);
|
||||||
|
let key_wide: Vec<u16> = reg_key.encode_utf16().chain(std::iter::once(0)).collect();
|
||||||
|
let val_wide: Vec<u16> = manifest_path
|
||||||
|
.encode_utf16()
|
||||||
|
.chain(std::iter::once(0))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut hkey = std::ptr::null_mut();
|
||||||
|
let mut disposition: u32 = 0;
|
||||||
|
let result = RegCreateKeyExW(
|
||||||
|
HKEY_CURRENT_USER,
|
||||||
|
key_wide.as_ptr(),
|
||||||
|
0,
|
||||||
|
std::ptr::null(),
|
||||||
|
REG_OPTION_NON_VOLATILE,
|
||||||
|
KEY_WRITE | KEY_WOW64_64KEY,
|
||||||
|
std::ptr::null(),
|
||||||
|
&mut hkey,
|
||||||
|
&mut disposition,
|
||||||
|
);
|
||||||
|
if result == 0 {
|
||||||
|
RegSetValueExW(
|
||||||
|
hkey,
|
||||||
|
std::ptr::null(),
|
||||||
|
0,
|
||||||
|
REG_SZ as u32,
|
||||||
|
val_wide.as_ptr() as *const u8,
|
||||||
|
(val_wide.len() * 2) as u32,
|
||||||
|
);
|
||||||
|
RegCloseKey(hkey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn delete_windows_manifest_registry_value(nmh_base_key: &str) {
|
||||||
|
use windows_sys::Win32::System::Registry::{
|
||||||
|
RegCloseKey, RegDeleteTreeW, RegOpenKeyExW, HKEY_CURRENT_USER,
|
||||||
|
KEY_WOW64_64KEY, KEY_WRITE,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let parent_wide: Vec<u16> = nmh_base_key
|
||||||
|
.encode_utf16()
|
||||||
|
.chain(std::iter::once(0))
|
||||||
|
.collect();
|
||||||
|
let name_wide: Vec<u16> = MANIFEST_NAME
|
||||||
|
.encode_utf16()
|
||||||
|
.chain(std::iter::once(0))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut parent_hkey = std::ptr::null_mut();
|
||||||
|
let result = RegOpenKeyExW(
|
||||||
|
HKEY_CURRENT_USER,
|
||||||
|
parent_wide.as_ptr(),
|
||||||
|
0,
|
||||||
|
KEY_WRITE | KEY_WOW64_64KEY,
|
||||||
|
&mut parent_hkey,
|
||||||
|
);
|
||||||
|
if result == 0 {
|
||||||
|
RegDeleteTreeW(parent_hkey, name_wide.as_ptr());
|
||||||
|
RegCloseKey(parent_hkey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Install the native messaging host manifests for all detected browsers.
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn install_native_host(form: &dyn ProgressCallback) -> Result<(), InstallError> {
|
||||||
|
form.update_progress_label("Installing native messaging host for modern browsers...", true);
|
||||||
|
|
||||||
|
let host_exe = get_native_host_exe_path();
|
||||||
|
let chrome_manifest_json = build_manifest_json(&host_exe, BrowserKind::ChromeLike);
|
||||||
|
let firefox_manifest_json = build_manifest_json(&host_exe, BrowserKind::Firefox);
|
||||||
|
|
||||||
|
// Create manifest directory inside the install dir.
|
||||||
|
let manifest_dir = get_native_host_install_dir().join("manifests");
|
||||||
|
let _ = fs::create_dir_all(&manifest_dir);
|
||||||
|
let chrome_manifest_path = manifest_dir.join(CHROME_MANIFEST_FILENAME);
|
||||||
|
fs::write(&chrome_manifest_path, chrome_manifest_json.as_bytes()).map_err(|e| {
|
||||||
|
InstallError::new(format!(
|
||||||
|
"Failed to write Chrome native messaging manifest: {}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
let firefox_manifest_path = manifest_dir.join(FIREFOX_MANIFEST_FILENAME);
|
||||||
|
fs::write(&firefox_manifest_path, firefox_manifest_json.as_bytes()).map_err(|e| {
|
||||||
|
InstallError::new(format!(
|
||||||
|
"Failed to write Firefox native messaging manifest: {}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let chrome_manifest_path_str = chrome_manifest_path.to_string_lossy().to_string();
|
||||||
|
let firefox_manifest_path_str = firefox_manifest_path.to_string_lossy().to_string();
|
||||||
|
|
||||||
|
// For each browser that is detected via registry, register the manifest.
|
||||||
|
for browser in WINDOWS_BROWSERS {
|
||||||
|
if !browser
|
||||||
|
.detect_keys
|
||||||
|
.iter()
|
||||||
|
.any(|detect_key| windows_registry_key_exists(detect_key))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let manifest_path = match browser.kind {
|
||||||
|
BrowserKind::Firefox => &firefox_manifest_path_str,
|
||||||
|
BrowserKind::ChromeLike => &chrome_manifest_path_str,
|
||||||
|
};
|
||||||
|
set_windows_manifest_registry_value(browser.native_messaging_reg_path, manifest_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
pub fn install_native_host(form: &dyn ProgressCallback) -> Result<(), InstallError> {
|
||||||
|
form.update_progress_label("Installing native messaging host for modern browsers...", true);
|
||||||
|
|
||||||
|
let host_exe = get_native_host_exe_path();
|
||||||
|
let chrome_manifest_json = build_manifest_json(&host_exe, BrowserKind::ChromeLike);
|
||||||
|
let firefox_manifest_json = build_manifest_json(&host_exe, BrowserKind::Firefox);
|
||||||
|
|
||||||
|
// Determine browser config directories that exist.
|
||||||
|
let home = std::env::var("HOME")
|
||||||
|
.map_err(|_| InstallError::new("HOME environment variable not set"))?;
|
||||||
|
let home = PathBuf::from(home);
|
||||||
|
|
||||||
|
// Check known browser paths and install manifests only for those that exist.
|
||||||
|
for target in get_non_windows_browser_targets(&home) {
|
||||||
|
if !target.detect_path.exists() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = fs::create_dir_all(&target.manifest_dir);
|
||||||
|
let manifest_filename = manifest_filename_for_kind(target.kind);
|
||||||
|
let manifest_json = match target.kind {
|
||||||
|
BrowserKind::Firefox => &firefox_manifest_json,
|
||||||
|
BrowserKind::ChromeLike => &chrome_manifest_json,
|
||||||
|
};
|
||||||
|
let manifest_path = target.manifest_dir.join(manifest_filename);
|
||||||
|
let _ = fs::write(&manifest_path, manifest_json.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Uninstall the native messaging host: remove manifests and registry entries.
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn uninstall_native_host(form: &dyn ProgressCallback) {
|
||||||
|
form.update_progress_label("Removing native messaging host...", true);
|
||||||
|
|
||||||
|
// Remove manifest files.
|
||||||
|
let install_dir = get_native_host_install_dir();
|
||||||
|
let manifest_dir = install_dir.join("manifests");
|
||||||
|
let _ = fs::remove_file(manifest_dir.join(CHROME_MANIFEST_FILENAME));
|
||||||
|
let _ = fs::remove_file(manifest_dir.join(FIREFOX_MANIFEST_FILENAME));
|
||||||
|
let _ = fs::remove_dir(&manifest_dir);
|
||||||
|
|
||||||
|
// Remove the host exe.
|
||||||
|
let host_exe = install_dir.join(HOST_BINARY_NAME);
|
||||||
|
crate::file_util::delete_file(&host_exe);
|
||||||
|
|
||||||
|
// Remove registry entries for all browsers.
|
||||||
|
for browser in WINDOWS_BROWSERS {
|
||||||
|
delete_windows_manifest_registry_value(browser.native_messaging_reg_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
pub fn uninstall_native_host(form: &dyn ProgressCallback) {
|
||||||
|
form.update_progress_label("Removing native messaging host...", true);
|
||||||
|
|
||||||
|
// Remove the host folder and everything inside it.
|
||||||
|
let install_dir = get_native_host_install_dir();
|
||||||
|
let host_exe = install_dir.join(HOST_BINARY_NAME);
|
||||||
|
let _ = fs::remove_file(&host_exe);
|
||||||
|
let _ = fs::remove_dir_all(&install_dir);
|
||||||
|
|
||||||
|
let home = match std::env::var("HOME") {
|
||||||
|
Ok(h) => PathBuf::from(h),
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
for target in get_non_windows_browser_targets(&home) {
|
||||||
|
let _ = fs::remove_file(target.manifest_dir.join(CHROME_MANIFEST_FILENAME));
|
||||||
|
let _ = fs::remove_file(target.manifest_dir.join(FIREFOX_MANIFEST_FILENAME));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a registry key exists under HKCU.
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn windows_registry_key_exists(subkey: &str) -> bool {
|
||||||
|
use windows_sys::Win32::System::Registry::{
|
||||||
|
RegCloseKey, RegOpenKeyExW, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE,
|
||||||
|
KEY_READ, KEY_WOW64_64KEY,
|
||||||
|
};
|
||||||
|
|
||||||
|
let key_wide: Vec<u16> = subkey.encode_utf16().chain(std::iter::once(0)).collect();
|
||||||
|
|
||||||
|
// Check HKCU first, then HKLM.
|
||||||
|
for &root in &[HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE] {
|
||||||
|
unsafe {
|
||||||
|
let mut hkey = std::ptr::null_mut();
|
||||||
|
let result = RegOpenKeyExW(root, key_wide.as_ptr(), 0, KEY_READ | KEY_WOW64_64KEY, &mut hkey);
|
||||||
|
if result == 0 {
|
||||||
|
RegCloseKey(hkey);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
@ -0,0 +1,110 @@
|
|||||||
|
use crate::ExitedProcess;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
use std::os::windows::process::CommandExt;
|
||||||
|
|
||||||
|
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||||
|
|
||||||
|
/// Run a process, capturing stdout and stderr, and wait for it to exit.
|
||||||
|
pub fn run_process(program: &str, args: &[&str]) -> ExitedProcess {
|
||||||
|
let mut cmd = Command::new(program);
|
||||||
|
cmd.args(args)
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||||
|
|
||||||
|
let result = cmd.output();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(output) => {
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
let combined = format!("{}{}", stdout.trim(), stderr.trim());
|
||||||
|
ExitedProcess {
|
||||||
|
exit_code: output.status.code().unwrap_or(-1),
|
||||||
|
output: combined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => ExitedProcess {
|
||||||
|
exit_code: -1,
|
||||||
|
output: e.to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run a process and wait for it to exit (no output capture).
|
||||||
|
pub fn run_unmanaged_process(program: &str, args: &[&str]) {
|
||||||
|
let mut cmd = Command::new(program);
|
||||||
|
cmd.args(args)
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||||
|
|
||||||
|
let _ = cmd.status();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect the names of DLL modules loaded in a given process.
|
||||||
|
/// Used to detect whether a browser has Flash DLLs loaded.
|
||||||
|
pub fn collect_modules(pid: u32) -> Vec<String> {
|
||||||
|
use windows_sys::Win32::Foundation::CloseHandle;
|
||||||
|
use windows_sys::Win32::System::ProcessStatus::{
|
||||||
|
EnumProcessModulesEx, GetModuleFileNameExW, LIST_MODULES_ALL,
|
||||||
|
};
|
||||||
|
use windows_sys::Win32::System::Threading::{
|
||||||
|
OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut modules = Vec::new();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, pid);
|
||||||
|
if handle.is_null() {
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
// HMODULE is *mut c_void on windows-sys 0.59
|
||||||
|
let mut h_modules: [*mut std::ffi::c_void; 1024] =
|
||||||
|
[std::ptr::null_mut(); 1024];
|
||||||
|
let mut cb_needed: u32 = 0;
|
||||||
|
|
||||||
|
let ok = EnumProcessModulesEx(
|
||||||
|
handle,
|
||||||
|
h_modules.as_mut_ptr().cast(),
|
||||||
|
std::mem::size_of_val(&h_modules) as u32,
|
||||||
|
&mut cb_needed,
|
||||||
|
LIST_MODULES_ALL,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ok != 0 {
|
||||||
|
let count =
|
||||||
|
cb_needed as usize / std::mem::size_of::<*mut std::ffi::c_void>();
|
||||||
|
for item in h_modules.iter().take(count.min(h_modules.len())) {
|
||||||
|
let mut name_buf = [0u16; 512];
|
||||||
|
let len = GetModuleFileNameExW(
|
||||||
|
handle,
|
||||||
|
*item as _,
|
||||||
|
name_buf.as_mut_ptr(),
|
||||||
|
name_buf.len() as u32,
|
||||||
|
);
|
||||||
|
if len > 0 {
|
||||||
|
let full_path =
|
||||||
|
String::from_utf16_lossy(&name_buf[..len as usize]);
|
||||||
|
if let Some(file_name) = full_path.rsplit('\\').next() {
|
||||||
|
modules.push(file_name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
modules
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/// Disable WoW64 file system redirection. Returns a cookie to restore later.
|
||||||
|
pub fn disable_redirection() -> *mut std::ffi::c_void {
|
||||||
|
let mut old_value: *mut std::ffi::c_void = std::ptr::null_mut();
|
||||||
|
unsafe {
|
||||||
|
Wow64DisableWow64FsRedirection(&mut old_value);
|
||||||
|
}
|
||||||
|
old_value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Re-enable WoW64 file system redirection using the cookie from `disable_redirection`.
|
||||||
|
pub fn enable_redirection(old_value: *mut std::ffi::c_void) {
|
||||||
|
unsafe {
|
||||||
|
Wow64RevertWow64FsRedirection(old_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "system" {
|
||||||
|
fn Wow64DisableWow64FsRedirection(old_value: *mut *mut std::ffi::c_void) -> i32;
|
||||||
|
fn Wow64RevertWow64FsRedirection(old_value: *mut std::ffi::c_void) -> i32;
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
use crate::{process_utils, system_info, InstallError};
|
||||||
|
use std::fs;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
/// Apply registry contents by writing a .reg file and importing with reg.exe.
|
||||||
|
pub fn apply_registry(entries: &[&str]) -> Result<(), InstallError> {
|
||||||
|
let combined = entries.join("\n\n");
|
||||||
|
let filled = system_info::fill_string(&combined);
|
||||||
|
|
||||||
|
let content = format!("Windows Registry Editor Version 5.00\n\n{}", filled);
|
||||||
|
|
||||||
|
let temp_dir = std::env::temp_dir();
|
||||||
|
// Include the process ID to avoid collisions when two instances run concurrently,
|
||||||
|
// matching the unique-file guarantee of C#'s Path.GetTempFileName().
|
||||||
|
let reg_file = temp_dir.join(format!("cleanflash_reg_{}.tmp", std::process::id()));
|
||||||
|
|
||||||
|
// Write as UTF-16LE with BOM (Windows .reg format).
|
||||||
|
{
|
||||||
|
let mut f = fs::File::create(®_file)
|
||||||
|
.map_err(|e| InstallError::new(format!("Failed to create temp reg file: {}", e)))?;
|
||||||
|
let utf16: Vec<u16> = content.encode_utf16().collect();
|
||||||
|
// BOM
|
||||||
|
f.write_all(&[0xFF, 0xFE])
|
||||||
|
.map_err(|e| InstallError::new(format!("Failed to write BOM: {}", e)))?;
|
||||||
|
for word in &utf16 {
|
||||||
|
f.write_all(&word.to_le_bytes())
|
||||||
|
.map_err(|e| InstallError::new(format!("Failed to write reg data: {}", e)))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let reg_filename = reg_file.to_string_lossy().to_string();
|
||||||
|
let result = process_utils::run_process("reg.exe", &["import", ®_filename]);
|
||||||
|
|
||||||
|
let _ = fs::remove_file(®_file);
|
||||||
|
|
||||||
|
if !result.is_successful() {
|
||||||
|
return Err(InstallError::new(format!(
|
||||||
|
"Failed to apply changes to registry: error code {}\n\n{}",
|
||||||
|
result.exit_code, result.output
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@ -0,0 +1,224 @@
|
|||||||
|
/// Embedded registry resources, matching the original C# resource strings.
|
||||||
|
|
||||||
|
pub const UNINSTALL_REGISTRY: &str = r#"[HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION]
|
||||||
|
"FlashHelperService.exe"=-
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Control Panel\Extended Properties\System.ControlPanel.Category]
|
||||||
|
"${SYSTEM_64_PATH}\\FlashPlayerCPLApp.cpl"=-
|
||||||
|
|
||||||
|
[-HKEY_CURRENT_USER\Software\FlashCenter]
|
||||||
|
[-HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\App Paths\FlashCenter.exe]
|
||||||
|
[-HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall\FlashCenter]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\AppID\{119DA84B-E3DB-4D47-A8DD-7FF6D5804689}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\AppID\{B9020634-CE8F-4F09-9FBC-D108A73A4676}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\TypeLib\{37EF68ED-16D3-4191-86BF-AB731D75AAB7}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\System\ControlSet001\services\Flash Helper Service]
|
||||||
|
[-HKEY_LOCAL_MACHINE\System\ControlSet001\services\FlashCenterService]
|
||||||
|
[-HKEY_LOCAL_MACHINE\System\CurrentControlSet\services\Flash Helper Service]
|
||||||
|
[-HKEY_LOCAL_MACHINE\System\CurrentControlSet\services\FlashCenterService]
|
||||||
|
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\MacromediaFlashPaper.MacromediaFlashPaper]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Low Rights\ElevationPolicy\{FAF199D2-BFA7-4394-A4DE-044A08E59B32}]
|
||||||
|
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\FlashPlayerUpdateService.exe]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\FlashUtil32_${VERSION_PATH}_ActiveX.exe]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\FlashUtil64_${VERSION_PATH}_ActiveX.exe]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\FlashUtil32_${VERSION_PATH}_Plugin.exe]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\FlashUtil64_${VERSION_PATH}_Plugin.exe]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\FlashPlayerPlugin_${VERSION_PATH}.exe]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\FlashUtil32_${VERSION_PATH}_pepper.exe]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\FlashUtil64_${VERSION_PATH}_pepper.exe]
|
||||||
|
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Internet Explorer\Low Rights\ElevationPolicy\{FAF199D2-BFA7-4394-A4DE-044A08E59B32}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Adobe Flash Player ActiveX]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Adobe Flash Player NPAPI]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Adobe Flash Player PPAPI]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\Clean Flash Player]
|
||||||
|
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\.mfp]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\.sol]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\.sor]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\.spl]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\.swf]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\AppID\{B9020634-CE8F-4F09-9FBC-D108A73A4676}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\CLSID\{B019E3BF-E7E5-453C-A2E4-D2C18CA0866F}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\CLSID\{D27CDB6E-AE6D-11cf-96B8-444553540000}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\FlashFactory.FlashFactory]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\FlashFactory.FlashFactory.1]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Interface\{299817DA-1FAC-4CE2-8F48-A108237013BD}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Interface\{307F64C0-621D-4D56-BBC6-91EFC13CE40D}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Interface\{57A0E747-3863-4D20-A811-950C84F1DB9B}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Interface\{86230738-D762-4C50-A2DE-A753E5B1686F}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Interface\{D27CDB6C-AE6D-11CF-96B8-444553540000}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Interface\{D27CDB6D-AE6D-11CF-96B8-444553540000}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\MIME\Database\Content Type\application/futuresplash]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\MIME\Database\Content Type\application/x-shockwave-flash]
|
||||||
|
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.1]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.2]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.3]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.4]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.5]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.6]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.7]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.8]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.9]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.10]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.11]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.12]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.13]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.14]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.15]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.16]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.17]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.18]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.19]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.20]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.21]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.22]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.23]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.24]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.25]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.26]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.27]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.28]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.29]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.30]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.31]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.32]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.33]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\ShockwaveFlash.ShockwaveFlash.34]
|
||||||
|
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\TypeLib\{57A0E746-3863-4D20-A811-950C84F1DB9B}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\TypeLib\{D27CDB6B-AE6D-11CF-96B8-444553540000}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\TypeLib\{FAB3E735-69C7-453B-A446-B6823C6DF1C9}]
|
||||||
|
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Macromedia]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\ActiveX Compatibility\{D27CDB6E-AE6D-11CF-96B8-444553540000}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\ActiveX Compatibility\{D27CDB70-AE6D-11cf-96B8-444553540000}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\NavigatorPluginsList\Shockwave Flash]
|
||||||
|
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\MozillaPlugins\@adobe.com/FlashPlayer]
|
||||||
|
|
||||||
|
[-HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\miniconfig]
|
||||||
|
[-HKEY_USERS\.DEFAULT\Software\Microsoft\Windows\CurrentVersion\miniconfig]"#;
|
||||||
|
|
||||||
|
pub const UNINSTALL_REGISTRY_64: &str = r#"[HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION]
|
||||||
|
"FlashHelperService.exe"=-
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Control Panel\Extended Properties\System.ControlPanel.Category]
|
||||||
|
"${SYSTEM_32_PATH}\\FlashPlayerCPLApp.cpl"=-
|
||||||
|
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Wow6432Node\CLSID\{B019E3BF-E7E5-453C-A2E4-D2C18CA0866F}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Wow6432Node\CLSID\{D27CDB6E-AE6D-11cf-96B8-444553540000}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Wow6432Node\CLSID\{D27CDB70-AE6D-11cf-96B8-444553540000}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Wow6432Node\Interface\{299817DA-1FAC-4CE2-8F48-A108237013BD}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Wow6432Node\Interface\{307F64C0-621D-4D56-BBC6-91EFC13CE40D}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Wow6432Node\Interface\{57A0E747-3863-4D20-A811-950C84F1DB9B}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Wow6432Node\Interface\{86230738-D762-4C50-A2DE-A753E5B1686F}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Wow6432Node\Interface\{D27CDB6C-AE6D-11CF-96B8-444553540000}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Classes\Wow6432Node\Interface\{D27CDB6D-AE6D-11CF-96B8-444553540000}]
|
||||||
|
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Wow6432Node\Macromedia]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Internet Explorer\ActiveX Compatibility\{D27CDB6E-AE6D-11CF-96B8-444553540000}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Internet Explorer\ActiveX Compatibility\{D27CDB70-AE6D-11cf-96B8-444553540000}]
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Internet Explorer\NavigatorPluginsList\Shockwave Flash]
|
||||||
|
|
||||||
|
[-HKEY_LOCAL_MACHINE\Software\Wow6432Node\MozillaPlugins\@adobe.com/FlashPlayer]"#;
|
||||||
|
|
||||||
|
pub const INSTALL_GENERAL: &str = r#"[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Control Panel\Extended Properties\System.ControlPanel.Category]
|
||||||
|
"${SYSTEM_32_PATH}\\FlashPlayerCPLApp.cpl"=dword:0000000a
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\FlashPlayerApp.exe]
|
||||||
|
"DisableExceptionChainValidation"=dword:00000000
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\Clean Flash Player]
|
||||||
|
"DisplayName"="Clean Flash Player ${VERSION}"
|
||||||
|
"HelpLink"="https://gitlab.com/cleanflash/installer#clean-flash-player"
|
||||||
|
"NoModify"=dword:00000001
|
||||||
|
"NoRepair"=dword:00000001
|
||||||
|
"URLInfoAbout"="https://gitlab.com/cleanflash/installer#clean-flash-player"
|
||||||
|
"URLUpdateInfo"="https://gitlab.com/cleanflash/installer#clean-flash-player"
|
||||||
|
"VersionMajor"=dword:00000022
|
||||||
|
"VersionMinor"=dword:00000000
|
||||||
|
"Publisher"="CleanFlash Team"
|
||||||
|
"EstimatedSize"=dword:00011cb8
|
||||||
|
"DisplayIcon"="${PROGRAM_FLASH_32_PATH}\\FlashUtil_Uninstall.exe"
|
||||||
|
"UninstallString"="${PROGRAM_FLASH_32_PATH}\\FlashUtil_Uninstall.exe"
|
||||||
|
"DisplayVersion"="${VERSION}""#;
|
||||||
|
|
||||||
|
pub const INSTALL_GENERAL_64: &str = r#"[HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Control Panel\Extended Properties\System.ControlPanel.Category]
|
||||||
|
"${SYSTEM_32_PATH}\\FlashPlayerCPLApp.cpl"=dword:0000000a
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\FlashPlayerApp.exe]
|
||||||
|
"DisableExceptionChainValidation"=dword:00000000"#;
|
||||||
|
|
||||||
|
pub const INSTALL_NP: &str = r#"[HKEY_LOCAL_MACHINE\Software\Macromedia\FlashPlayerPlugin]
|
||||||
|
"isPartner"=dword:00000001
|
||||||
|
"Version"="${VERSION}"
|
||||||
|
"PlayerPath"="${FLASH_64_PATH}\\NPSWF${ARCH}_${VERSION_PATH}.dll"
|
||||||
|
"UninstallerPath"=-
|
||||||
|
"isScriptDebugger"=dword:00000000
|
||||||
|
"isESR"=dword:00000000
|
||||||
|
"isMSI"=dword:00000000
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\Software\Macromedia\FlashPlayerPluginReleaseType]
|
||||||
|
"Release"=dword:00000001
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\Software\MozillaPlugins\@adobe.com/FlashPlayer]
|
||||||
|
"Vendor"="Adobe"
|
||||||
|
"ProductName"="Adobe® Flash® Player ${VERSION} Plugin"
|
||||||
|
"Path"="${FLASH_64_PATH}\\NPSWF${ARCH}_${VERSION_PATH}.dll"
|
||||||
|
"Version"="${VERSION}"
|
||||||
|
"Description"="Adobe® Flash® Player ${VERSION} Plugin"
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\FlashPlayerPlugin_${VERSION_PATH}.exe]
|
||||||
|
"DisableExceptionChainValidation"=dword:00000000"#;
|
||||||
|
|
||||||
|
pub const INSTALL_NP_64: &str = r#"[HKEY_LOCAL_MACHINE\Software\Wow6432Node\Macromedia\FlashPlayerPlugin]
|
||||||
|
"PlayerPath"="${FLASH_32_PATH}\\NPSWF_${VERSION_PATH}.dll"
|
||||||
|
"Version"="${VERSION}"
|
||||||
|
"UninstallerPath"=-
|
||||||
|
"isScriptDebugger"=dword:00000000
|
||||||
|
"isESR"=dword:00000000
|
||||||
|
"isMSI"=dword:00000000
|
||||||
|
"isPartner"=dword:00000001
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\Software\Wow6432Node\Macromedia\FlashPlayerPluginReleaseType]
|
||||||
|
"Release"=dword:00000001
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\Software\Wow6432Node\MozillaPlugins\@adobe.com/FlashPlayer]
|
||||||
|
"ProductName"="Adobe® Flash® Player ${VERSION} Plugin"
|
||||||
|
"Description"="Adobe® Flash® Player ${VERSION} Plugin"
|
||||||
|
"Version"="${VERSION}"
|
||||||
|
"XPTPath"="${FLASH_32_PATH}\\flashplayer.xpt"
|
||||||
|
"Vendor"="Adobe"
|
||||||
|
"Path"="${FLASH_32_PATH}\\NPSWF32_${VERSION_PATH}.dll"
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\FlashPlayerPlugin_${VERSION_PATH}.exe]
|
||||||
|
"DisableExceptionChainValidation"=dword:00000000"#;
|
||||||
|
|
||||||
|
pub const INSTALL_PP: &str = r#"[HKEY_LOCAL_MACHINE\Software\Macromedia\FlashPlayerPepper]
|
||||||
|
"UninstallerPath"=-
|
||||||
|
"PlayerPath"="${FLASH_64_PATH}\\pepflashplayer${ARCH}_${VERSION_PATH}.dll"
|
||||||
|
"isScriptDebugger"=dword:00000000
|
||||||
|
"isESR"=dword:00000000
|
||||||
|
"isMSI"=dword:00000000
|
||||||
|
"isPartner"=dword:00000001
|
||||||
|
"Version"="${VERSION}"
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\Software\Macromedia\FlashPlayerPepperReleaseType]
|
||||||
|
"Release"=dword:00000001"#;
|
||||||
|
|
||||||
|
pub const INSTALL_PP_64: &str = r#"[HKEY_LOCAL_MACHINE\Software\Wow6432Node\Macromedia\FlashPlayerPepper]
|
||||||
|
"UninstallerPath"=-
|
||||||
|
"PlayerPath"="${FLASH_32_PATH}\\pepflashplayer32_${VERSION_PATH}.dll"
|
||||||
|
"isScriptDebugger"=dword:00000000
|
||||||
|
"isESR"=dword:00000000
|
||||||
|
"isMSI"=dword:00000000
|
||||||
|
"isPartner"=dword:00000001
|
||||||
|
"Version"="${VERSION}"
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\Software\Wow6432Node\Macromedia\FlashPlayerPepperReleaseType]
|
||||||
|
"Release"=dword:00000001"#;
|
||||||
@ -0,0 +1,187 @@
|
|||||||
|
use crate::update_checker;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::env;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// Lazily-initialized system path info, analogous to C# SystemInfo class.
|
||||||
|
pub struct SystemInfo {
|
||||||
|
pub system32_path: PathBuf,
|
||||||
|
pub system64_path: PathBuf,
|
||||||
|
pub program32_path: PathBuf,
|
||||||
|
pub flash_program32_path: PathBuf,
|
||||||
|
pub macromed32_path: PathBuf,
|
||||||
|
pub macromed64_path: PathBuf,
|
||||||
|
pub flash32_path: PathBuf,
|
||||||
|
pub flash64_path: PathBuf,
|
||||||
|
pub version: String,
|
||||||
|
pub version_path: String,
|
||||||
|
pub version_comma: String,
|
||||||
|
pub is_64bit: bool,
|
||||||
|
replacements: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SystemInfo {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let system32_path = get_syswow64_path();
|
||||||
|
let system64_path = get_system32_path();
|
||||||
|
let program32_path = get_program_files_x86();
|
||||||
|
let flash_program32_path = program32_path.join("Flash Player");
|
||||||
|
let macromed32_path = system32_path.join("Macromed");
|
||||||
|
let macromed64_path = system64_path.join("Macromed");
|
||||||
|
let flash32_path = macromed32_path.join("Flash");
|
||||||
|
let flash64_path = macromed64_path.join("Flash");
|
||||||
|
let version = update_checker::FLASH_VERSION.to_string();
|
||||||
|
let version_path = version.replace('.', "_");
|
||||||
|
let version_comma = version.replace('.', ",");
|
||||||
|
let is_64bit = cfg!(target_pointer_width = "64")
|
||||||
|
|| env::var("PROCESSOR_ARCHITEW6432").is_ok();
|
||||||
|
|
||||||
|
let arch = if is_64bit { "64" } else { "32" };
|
||||||
|
|
||||||
|
let mut replacements = HashMap::new();
|
||||||
|
replacements.insert(
|
||||||
|
"${SYSTEM_32_PATH}".into(),
|
||||||
|
system32_path.to_string_lossy().replace('\\', "\\\\"),
|
||||||
|
);
|
||||||
|
replacements.insert(
|
||||||
|
"${SYSTEM_64_PATH}".into(),
|
||||||
|
system64_path.to_string_lossy().replace('\\', "\\\\"),
|
||||||
|
);
|
||||||
|
replacements.insert(
|
||||||
|
"${PROGRAM_32_PATH}".into(),
|
||||||
|
program32_path.to_string_lossy().replace('\\', "\\\\"),
|
||||||
|
);
|
||||||
|
replacements.insert(
|
||||||
|
"${PROGRAM_FLASH_32_PATH}".into(),
|
||||||
|
flash_program32_path.to_string_lossy().replace('\\', "\\\\"),
|
||||||
|
);
|
||||||
|
replacements.insert(
|
||||||
|
"${FLASH_32_PATH}".into(),
|
||||||
|
flash32_path.to_string_lossy().replace('\\', "\\\\"),
|
||||||
|
);
|
||||||
|
replacements.insert(
|
||||||
|
"${FLASH_64_PATH}".into(),
|
||||||
|
flash64_path.to_string_lossy().replace('\\', "\\\\"),
|
||||||
|
);
|
||||||
|
replacements.insert("${VERSION}".into(), version.clone());
|
||||||
|
replacements.insert("${VERSION_PATH}".into(), version_path.clone());
|
||||||
|
replacements.insert("${VERSION_COMMA}".into(), version_comma.clone());
|
||||||
|
replacements.insert("${ARCH}".into(), arch.into());
|
||||||
|
|
||||||
|
Self {
|
||||||
|
system32_path,
|
||||||
|
system64_path,
|
||||||
|
program32_path,
|
||||||
|
flash_program32_path,
|
||||||
|
macromed32_path,
|
||||||
|
macromed64_path,
|
||||||
|
flash32_path,
|
||||||
|
flash64_path,
|
||||||
|
version,
|
||||||
|
version_path,
|
||||||
|
version_comma,
|
||||||
|
is_64bit,
|
||||||
|
replacements,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn system_paths(&self) -> Vec<&Path> {
|
||||||
|
if self.is_64bit {
|
||||||
|
vec![&self.system32_path, &self.system64_path]
|
||||||
|
} else {
|
||||||
|
vec![&self.system32_path]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn macromed_paths(&self) -> Vec<&Path> {
|
||||||
|
if self.is_64bit {
|
||||||
|
vec![&self.macromed32_path, &self.macromed64_path]
|
||||||
|
} else {
|
||||||
|
vec![&self.macromed32_path]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fill_string(&self, s: &str) -> String {
|
||||||
|
let mut result = s.to_string();
|
||||||
|
for (key, value) in &self.replacements {
|
||||||
|
result = result.replace(key.as_str(), value);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn is_legacy_windows(&self) -> bool {
|
||||||
|
// Windows version < 6.2 (before Windows 8).
|
||||||
|
unsafe {
|
||||||
|
let mut info: windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW =
|
||||||
|
std::mem::zeroed();
|
||||||
|
info.dwOSVersionInfoSize =
|
||||||
|
std::mem::size_of::<windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW>()
|
||||||
|
as u32;
|
||||||
|
// RtlGetVersion always succeeds and isn't deprecated like GetVersionEx.
|
||||||
|
rtl_get_version(&mut info);
|
||||||
|
info.dwMajorVersion < 6
|
||||||
|
|| (info.dwMajorVersion == 6 && info.dwMinorVersion < 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
pub fn is_legacy_windows(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
extern "system" {
|
||||||
|
fn RtlGetVersion(
|
||||||
|
lp_version_information: *mut windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW,
|
||||||
|
) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
unsafe fn rtl_get_version(
|
||||||
|
info: &mut windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW,
|
||||||
|
) {
|
||||||
|
RtlGetVersion(info as *mut _);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Global convenience: fill replacement strings using a default SystemInfo.
|
||||||
|
pub fn fill_string(s: &str) -> String {
|
||||||
|
SYSTEM_INFO.with(|si| si.fill_string(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static SYSTEM_INFO: SystemInfo = SystemInfo::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_system_info<F, R>(f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&SystemInfo) -> R,
|
||||||
|
{
|
||||||
|
SYSTEM_INFO.with(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_system32_path() -> PathBuf {
|
||||||
|
PathBuf::from(env::var("SYSTEMROOT").unwrap_or_else(|_| r"C:\Windows".into()))
|
||||||
|
.join("System32")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_syswow64_path() -> PathBuf {
|
||||||
|
let root = env::var("SYSTEMROOT").unwrap_or_else(|_| r"C:\Windows".into());
|
||||||
|
let wow64 = PathBuf::from(&root).join("SysWOW64");
|
||||||
|
if wow64.exists() {
|
||||||
|
wow64
|
||||||
|
} else {
|
||||||
|
PathBuf::from(&root).join("System32")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_program_files_x86() -> PathBuf {
|
||||||
|
if let Ok(pf86) = env::var("PROGRAMFILES(X86)") {
|
||||||
|
PathBuf::from(pf86)
|
||||||
|
} else if let Ok(pf) = env::var("PROGRAMFILES") {
|
||||||
|
PathBuf::from(pf)
|
||||||
|
} else {
|
||||||
|
PathBuf::from(r"C:\Program Files")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,349 @@
|
|||||||
|
use crate::{
|
||||||
|
file_util, native_host, process_utils, registry, resources, system_info, winapi_helpers, InstallError,
|
||||||
|
ProgressCallback,
|
||||||
|
};
|
||||||
|
use std::env;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
const PROCESSES_TO_KILL: &[&str] = &[
|
||||||
|
"fcbrowser",
|
||||||
|
"fcbrowsermanager",
|
||||||
|
"fclogin",
|
||||||
|
"fctips",
|
||||||
|
"flashcenter",
|
||||||
|
"flashcenterservice",
|
||||||
|
"flashcenteruninst",
|
||||||
|
"flashplay",
|
||||||
|
"update",
|
||||||
|
"wow_helper",
|
||||||
|
"dummy_cmd",
|
||||||
|
"flashhelperservice",
|
||||||
|
"flashplayerapp",
|
||||||
|
"flashplayer_sa",
|
||||||
|
"flashplayer_sa_debug",
|
||||||
|
"flash-player-host",
|
||||||
|
];
|
||||||
|
|
||||||
|
const CONDITIONAL_PROCESSES: &[&str] = &[
|
||||||
|
"plugin-container",
|
||||||
|
"opera",
|
||||||
|
"iexplore",
|
||||||
|
"chrome",
|
||||||
|
"chromium",
|
||||||
|
"brave",
|
||||||
|
"vivaldi",
|
||||||
|
"msedge",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Unregister an ActiveX OCX file via regsvr32.
|
||||||
|
pub fn unregister_activex(filename: &str) -> Result<(), InstallError> {
|
||||||
|
winapi_helpers::allow_modifications();
|
||||||
|
|
||||||
|
let path = Path::new(filename);
|
||||||
|
let dir = path.parent().unwrap_or(Path::new("."));
|
||||||
|
let file_name = path
|
||||||
|
.file_name()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let _prev = env::current_dir();
|
||||||
|
let _ = env::set_current_dir(dir);
|
||||||
|
|
||||||
|
let process = process_utils::run_process("regsvr32.exe", &["/s", "/u", &file_name]);
|
||||||
|
if !process.is_successful() {
|
||||||
|
return Err(InstallError::new(format!(
|
||||||
|
"Failed to unregister ActiveX plugin: error code {}\n\n{}",
|
||||||
|
process.exit_code, process.output
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uninstall_registry() -> Result<(), InstallError> {
|
||||||
|
system_info::with_system_info(|si| {
|
||||||
|
if si.is_64bit {
|
||||||
|
registry::apply_registry(&[
|
||||||
|
resources::UNINSTALL_REGISTRY,
|
||||||
|
resources::UNINSTALL_REGISTRY_64,
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
registry::apply_registry(&[resources::UNINSTALL_REGISTRY])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_task(task: &str) {
|
||||||
|
process_utils::run_unmanaged_process("schtasks.exe", &["/delete", "/tn", task, "/f"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop_service(service: &str) {
|
||||||
|
process_utils::run_unmanaged_process("net.exe", &["stop", service]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_service(service: &str) {
|
||||||
|
stop_service(service);
|
||||||
|
process_utils::run_unmanaged_process("sc.exe", &["delete", service]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_flash_center() {
|
||||||
|
// Remove Flash Center from Program Files.
|
||||||
|
let pf = env::var("PROGRAMFILES").unwrap_or_default();
|
||||||
|
file_util::wipe_folder(&PathBuf::from(&pf).join("FlashCenter"));
|
||||||
|
|
||||||
|
if let Ok(pf86) = env::var("PROGRAMFILES(X86)") {
|
||||||
|
file_util::wipe_folder(&PathBuf::from(&pf86).join("FlashCenter"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove start menu shortcuts.
|
||||||
|
if let Some(appdata) = env::var("PROGRAMDATA").ok() {
|
||||||
|
file_util::wipe_folder(
|
||||||
|
&PathBuf::from(&appdata)
|
||||||
|
.join("Microsoft")
|
||||||
|
.join("Windows")
|
||||||
|
.join("Start Menu")
|
||||||
|
.join("Programs")
|
||||||
|
.join("Flash Center"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Flash Center cache / user data.
|
||||||
|
if let Some(local) = env::var("LOCALAPPDATA").ok() {
|
||||||
|
file_util::wipe_folder(&PathBuf::from(&local).join("Flash_Center"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove common start menu shortcuts.
|
||||||
|
if let Some(appdata) = env::var("APPDATA").ok() {
|
||||||
|
file_util::wipe_folder(
|
||||||
|
&PathBuf::from(&appdata)
|
||||||
|
.join("Microsoft")
|
||||||
|
.join("Windows")
|
||||||
|
.join("Start Menu")
|
||||||
|
.join("Programs")
|
||||||
|
.join("Flash Center"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Desktop shortcuts.
|
||||||
|
if let Some(desktop) = get_common_desktop() {
|
||||||
|
file_util::delete_file(&desktop.join("Flash Center.lnk"));
|
||||||
|
}
|
||||||
|
if let Some(desktop) = dirs_desktop() {
|
||||||
|
file_util::delete_file(&desktop.join("Flash Player.lnk"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Flash Player from Program Files.
|
||||||
|
system_info::with_system_info(|si| {
|
||||||
|
file_util::wipe_folder(&si.flash_program32_path);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up temp folder spyware remnants.
|
||||||
|
let temp = env::temp_dir();
|
||||||
|
if let Ok(entries) = std::fs::read_dir(&temp) {
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let name = entry.file_name().to_string_lossy().to_string();
|
||||||
|
if name.len() == 11 && name.ends_with(".tmp") && entry.path().is_dir() {
|
||||||
|
let _ = file_util::wipe_folder(&entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Quick Launch shortcuts from Internet Explorer.
|
||||||
|
if let Ok(appdata) = env::var("APPDATA") {
|
||||||
|
file_util::recursive_delete(
|
||||||
|
&PathBuf::from(&appdata)
|
||||||
|
.join("Microsoft")
|
||||||
|
.join("Internet Explorer")
|
||||||
|
.join("Quick Launch"),
|
||||||
|
Some("Flash Center.lnk"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Flash Player shortcut from the user's Start Menu root.
|
||||||
|
if let Ok(appdata) = env::var("APPDATA") {
|
||||||
|
file_util::delete_file(
|
||||||
|
&PathBuf::from(appdata)
|
||||||
|
.join("Microsoft")
|
||||||
|
.join("Windows")
|
||||||
|
.join("Start Menu")
|
||||||
|
.join("Flash Player.lnk"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_flash_player() {
|
||||||
|
system_info::with_system_info(|si| {
|
||||||
|
// Remove Macromed folders.
|
||||||
|
for dir in si.macromed_paths() {
|
||||||
|
file_util::recursive_delete(dir, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Flash Player control panel apps.
|
||||||
|
for sys_dir in si.system_paths() {
|
||||||
|
file_util::delete_file(&sys_dir.join("FlashPlayerApp.exe"));
|
||||||
|
file_util::delete_file(&sys_dir.join("FlashPlayerCPLApp.cpl"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_kill_conditional_process(name: &str, pid: u32) -> bool {
|
||||||
|
if !CONDITIONAL_PROCESSES
|
||||||
|
.iter()
|
||||||
|
.any(|p| p.eq_ignore_ascii_case(name))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let modules = process_utils::collect_modules(pid);
|
||||||
|
modules.iter().any(|m| {
|
||||||
|
let lower = m.to_lowercase();
|
||||||
|
lower.starts_with("flash32")
|
||||||
|
|| lower.starts_with("flash64")
|
||||||
|
|| lower.starts_with("libpepflash")
|
||||||
|
|| lower.starts_with("npswf")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop_processes() {
|
||||||
|
use windows_sys::Win32::Foundation::CloseHandle;
|
||||||
|
use windows_sys::Win32::System::Threading::{
|
||||||
|
OpenProcess, TerminateProcess, WaitForSingleObject, PROCESS_QUERY_INFORMATION,
|
||||||
|
PROCESS_TERMINATE, PROCESS_VM_READ,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enumerate all processes via the snapshot API.
|
||||||
|
let mut pids = enumerate_processes();
|
||||||
|
|
||||||
|
// Sort by creation time (oldest first), matching C#'s .OrderBy(o => o.StartTime).
|
||||||
|
pids.sort_by_key(|(pid, _)| get_process_creation_time(*pid));
|
||||||
|
|
||||||
|
for (pid, name) in &pids {
|
||||||
|
let lower = name.to_lowercase();
|
||||||
|
let should_kill = PROCESSES_TO_KILL.iter().any(|p| *p == lower)
|
||||||
|
|| should_kill_conditional_process(&lower, *pid);
|
||||||
|
|
||||||
|
if !should_kill {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let handle = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, *pid);
|
||||||
|
if !handle.is_null() {
|
||||||
|
TerminateProcess(handle, 1);
|
||||||
|
WaitForSingleObject(handle, 5000);
|
||||||
|
CloseHandle(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_process_creation_time(pid: u32) -> u64 {
|
||||||
|
use windows_sys::Win32::Foundation::{CloseHandle, FILETIME};
|
||||||
|
use windows_sys::Win32::System::Threading::{GetProcessTimes, OpenProcess, PROCESS_QUERY_INFORMATION};
|
||||||
|
unsafe {
|
||||||
|
let handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid);
|
||||||
|
if handle.is_null() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let mut create = FILETIME { dwLowDateTime: 0, dwHighDateTime: 0 };
|
||||||
|
let mut exit_time = FILETIME { dwLowDateTime: 0, dwHighDateTime: 0 };
|
||||||
|
let mut kernel_time = FILETIME { dwLowDateTime: 0, dwHighDateTime: 0 };
|
||||||
|
let mut user_time = FILETIME { dwLowDateTime: 0, dwHighDateTime: 0 };
|
||||||
|
GetProcessTimes(handle, &mut create, &mut exit_time, &mut kernel_time, &mut user_time);
|
||||||
|
CloseHandle(handle);
|
||||||
|
((create.dwHighDateTime as u64) << 32) | create.dwLowDateTime as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enumerate_processes() -> Vec<(u32, String)> {
|
||||||
|
use windows_sys::Win32::System::ProcessStatus::{EnumProcesses, GetModuleBaseNameW};
|
||||||
|
use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ};
|
||||||
|
use windows_sys::Win32::Foundation::CloseHandle;
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
let mut pids = [0u32; 4096];
|
||||||
|
let mut bytes_returned: u32 = 0;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if EnumProcesses(
|
||||||
|
pids.as_mut_ptr(),
|
||||||
|
std::mem::size_of_val(&pids) as u32,
|
||||||
|
&mut bytes_returned,
|
||||||
|
) == 0
|
||||||
|
{
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = bytes_returned as usize / std::mem::size_of::<u32>();
|
||||||
|
|
||||||
|
for &pid in &pids[..count] {
|
||||||
|
if pid == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, pid);
|
||||||
|
if handle.is_null() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut name_buf = [0u16; 260];
|
||||||
|
let len = GetModuleBaseNameW(handle, std::ptr::null_mut(), name_buf.as_mut_ptr(), 260);
|
||||||
|
CloseHandle(handle);
|
||||||
|
|
||||||
|
if len > 0 {
|
||||||
|
let name = String::from_utf16_lossy(&name_buf[..len as usize]);
|
||||||
|
// Strip .exe suffix for matching.
|
||||||
|
let base = name.strip_suffix(".exe").unwrap_or(&name).to_string();
|
||||||
|
results.push((pid, base));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform the full uninstallation sequence.
|
||||||
|
pub fn uninstall(form: &dyn ProgressCallback) -> Result<(), InstallError> {
|
||||||
|
winapi_helpers::allow_modifications();
|
||||||
|
|
||||||
|
form.update_progress_label("Stopping Flash auto-updater task...", true);
|
||||||
|
delete_task("Adobe Flash Player Updater");
|
||||||
|
delete_task("Adobe Flash Player Updater");
|
||||||
|
|
||||||
|
form.update_progress_label("Stopping Flash auto-updater service...", true);
|
||||||
|
delete_service("AdobeFlashPlayerUpdateSvc");
|
||||||
|
|
||||||
|
form.update_progress_label("Stopping Flash Center services...", true);
|
||||||
|
delete_service("Flash Helper Service");
|
||||||
|
form.tick_progress();
|
||||||
|
delete_service("FlashCenterService");
|
||||||
|
|
||||||
|
form.update_progress_label("Exiting all browsers...", true);
|
||||||
|
stop_processes();
|
||||||
|
|
||||||
|
form.update_progress_label("Cleaning up registry...", true);
|
||||||
|
uninstall_registry()?;
|
||||||
|
|
||||||
|
form.update_progress_label("Removing Flash Center...", true);
|
||||||
|
delete_flash_center();
|
||||||
|
|
||||||
|
form.update_progress_label("Removing Flash Player...", true);
|
||||||
|
delete_flash_player();
|
||||||
|
|
||||||
|
native_host::uninstall_native_host(form);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to get common desktop path.
|
||||||
|
fn get_common_desktop() -> Option<PathBuf> {
|
||||||
|
env::var("PUBLIC")
|
||||||
|
.ok()
|
||||||
|
.map(|p| PathBuf::from(p).join("Desktop"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dirs_desktop() -> Option<PathBuf> {
|
||||||
|
env::var("USERPROFILE")
|
||||||
|
.ok()
|
||||||
|
.map(|p| PathBuf::from(p).join("Desktop"))
|
||||||
|
}
|
||||||
@ -0,0 +1,149 @@
|
|||||||
|
pub const FLASH_VERSION: &str = "34.0.0.330";
|
||||||
|
pub const VERSION: &str = "34.0.0.330";
|
||||||
|
|
||||||
|
const API_HOST: &str = "api.github.com";
|
||||||
|
const API_PATH: &str = "/repos/cleanflash/installer/releases/latest";
|
||||||
|
const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36";
|
||||||
|
|
||||||
|
pub struct VersionInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_latest_version() -> Option<VersionInfo> {
|
||||||
|
let json = fetch_https(API_HOST, API_PATH, USER_AGENT)?;
|
||||||
|
let name = extract_json_string(&json, "name")?;
|
||||||
|
let tag = extract_json_string(&json, "tag_name")?;
|
||||||
|
let url = extract_json_string(&json, "html_url")?;
|
||||||
|
|
||||||
|
// Validate the URL to guard against a malicious/unexpected response.
|
||||||
|
if !url.starts_with("https://") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Latest release: {} ({}) {}", name, tag, url);
|
||||||
|
Some(VersionInfo { name, version: tag, url })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform an HTTPS GET request using WinHTTP and return the response body as UTF-8.
|
||||||
|
fn fetch_https(host: &str, path: &str, user_agent: &str) -> Option<String> {
|
||||||
|
use windows_sys::Win32::Networking::WinHttp::{
|
||||||
|
WinHttpCloseHandle, WinHttpConnect, WinHttpOpen, WinHttpOpenRequest,
|
||||||
|
WinHttpQueryDataAvailable, WinHttpReadData, WinHttpReceiveResponse,
|
||||||
|
WinHttpSendRequest, WINHTTP_FLAG_SECURE,
|
||||||
|
};
|
||||||
|
|
||||||
|
let wide = |s: &str| -> Vec<u16> { s.encode_utf16().chain(std::iter::once(0)).collect() };
|
||||||
|
|
||||||
|
let agent_w = wide(user_agent);
|
||||||
|
let host_w = wide(host);
|
||||||
|
let path_w = wide(path);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Open session with system default proxy settings (WINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0).
|
||||||
|
let session = WinHttpOpen(agent_w.as_ptr(), 0, std::ptr::null(), std::ptr::null(), 0);
|
||||||
|
if session.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let connection = WinHttpConnect(session, host_w.as_ptr(), 443, 0);
|
||||||
|
if connection.is_null() {
|
||||||
|
WinHttpCloseHandle(session);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open a GET request over HTTPS (null verb = GET, null version = HTTP/1.1).
|
||||||
|
let request = WinHttpOpenRequest(
|
||||||
|
connection,
|
||||||
|
std::ptr::null(),
|
||||||
|
path_w.as_ptr(),
|
||||||
|
std::ptr::null(),
|
||||||
|
std::ptr::null(),
|
||||||
|
std::ptr::null(),
|
||||||
|
WINHTTP_FLAG_SECURE,
|
||||||
|
);
|
||||||
|
if request.is_null() {
|
||||||
|
WinHttpCloseHandle(connection);
|
||||||
|
WinHttpCloseHandle(session);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if WinHttpSendRequest(request, std::ptr::null(), 0, std::ptr::null(), 0, 0, 0) == 0 {
|
||||||
|
WinHttpCloseHandle(request);
|
||||||
|
WinHttpCloseHandle(connection);
|
||||||
|
WinHttpCloseHandle(session);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if WinHttpReceiveResponse(request, std::ptr::null_mut()) == 0 {
|
||||||
|
WinHttpCloseHandle(request);
|
||||||
|
WinHttpCloseHandle(connection);
|
||||||
|
WinHttpCloseHandle(session);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut response: Vec<u8> = Vec::new();
|
||||||
|
loop {
|
||||||
|
let mut available: u32 = 0;
|
||||||
|
if WinHttpQueryDataAvailable(request, &mut available) == 0 || available == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let offset = response.len();
|
||||||
|
response.resize(offset + available as usize, 0);
|
||||||
|
let mut read: u32 = 0;
|
||||||
|
if WinHttpReadData(
|
||||||
|
request,
|
||||||
|
response[offset..].as_mut_ptr() as *mut _,
|
||||||
|
available,
|
||||||
|
&mut read,
|
||||||
|
) == 0
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
response.truncate(offset + read as usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
WinHttpCloseHandle(request);
|
||||||
|
WinHttpCloseHandle(connection);
|
||||||
|
WinHttpCloseHandle(session);
|
||||||
|
|
||||||
|
String::from_utf8(response).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract a JSON string value for the given key from a JSON object.
|
||||||
|
/// Handles the escape sequences that appear in GitHub API responses.
|
||||||
|
fn extract_json_string(json: &str, key: &str) -> Option<String> {
|
||||||
|
let search = format!("\"{}\"", key);
|
||||||
|
let key_pos = json.find(&search)?;
|
||||||
|
let rest = &json[key_pos + search.len()..];
|
||||||
|
let colon = rest.find(':')?;
|
||||||
|
let rest = rest[colon + 1..].trim_start();
|
||||||
|
if !rest.starts_with('"') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let rest = &rest[1..];
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut chars = rest.chars();
|
||||||
|
loop {
|
||||||
|
match chars.next()? {
|
||||||
|
'"' => break,
|
||||||
|
'\\' => match chars.next()? {
|
||||||
|
'"' => result.push('"'),
|
||||||
|
'\\' => result.push('\\'),
|
||||||
|
'/' => result.push('/'),
|
||||||
|
'n' => result.push('\n'),
|
||||||
|
'r' => result.push('\r'),
|
||||||
|
't' => result.push('\t'),
|
||||||
|
c => {
|
||||||
|
result.push('\\');
|
||||||
|
result.push(c);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
c => result.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
/// Enable SeRestorePrivilege and SeTakeOwnershipPrivilege for the current process.
|
||||||
|
pub fn allow_modifications() {
|
||||||
|
let _ = modify_privilege("SeRestorePrivilege\0", true);
|
||||||
|
let _ = modify_privilege("SeTakeOwnershipPrivilege\0", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn modify_privilege(name: &str, enable: bool) -> Result<(), ()> {
|
||||||
|
use windows_sys::Win32::Foundation::CloseHandle;
|
||||||
|
use windows_sys::Win32::Security::{
|
||||||
|
AdjustTokenPrivileges, LookupPrivilegeValueW, LUID_AND_ATTRIBUTES,
|
||||||
|
SE_PRIVILEGE_ENABLED, TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES, TOKEN_QUERY,
|
||||||
|
};
|
||||||
|
use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut token: *mut std::ffi::c_void = std::ptr::null_mut();
|
||||||
|
if OpenProcessToken(
|
||||||
|
GetCurrentProcess(),
|
||||||
|
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
|
||||||
|
&mut token,
|
||||||
|
) == 0
|
||||||
|
{
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let wide_name: Vec<u16> = name.encode_utf16().collect();
|
||||||
|
let mut luid = std::mem::zeroed();
|
||||||
|
if LookupPrivilegeValueW(std::ptr::null(), wide_name.as_ptr(), &mut luid) == 0 {
|
||||||
|
CloseHandle(token);
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut tp = TOKEN_PRIVILEGES {
|
||||||
|
PrivilegeCount: 1,
|
||||||
|
Privileges: [LUID_AND_ATTRIBUTES {
|
||||||
|
Luid: luid,
|
||||||
|
Attributes: if enable { SE_PRIVILEGE_ENABLED } else { 0 },
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = AdjustTokenPrivileges(
|
||||||
|
token,
|
||||||
|
0,
|
||||||
|
&mut tp,
|
||||||
|
std::mem::size_of::<TOKEN_PRIVILEGES>() as u32,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
);
|
||||||
|
|
||||||
|
CloseHandle(token);
|
||||||
|
|
||||||
|
if result == 0 {
|
||||||
|
Err(())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "clean_flash_installer"
|
||||||
|
version = "34.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["FlashPatch Team"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clean_flash_common = { path = "../clean_flash_common" }
|
||||||
|
clean_flash_ui = { path = "../clean_flash_ui" }
|
||||||
|
minifb = { workspace = true }
|
||||||
|
windows-sys = { workspace = true }
|
||||||
|
sevenz-rust2 = { workspace = true }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
winresource = "0.1"
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
fn main() {
|
||||||
|
// Set application icon from .ico resource (if present).
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
let mut res = winresource::WindowsResource::new();
|
||||||
|
// Attempt to set icon; ignore if file doesn't exist.
|
||||||
|
if std::path::Path::new("../../resources/icon.ico").exists() {
|
||||||
|
res.set_icon("../../resources/icon.ico");
|
||||||
|
}
|
||||||
|
res.set("ProductName", "Clean Flash Player Installer");
|
||||||
|
res.set("FileDescription", "Clean Flash Player Installer");
|
||||||
|
res.set("ProductVersion", "34.0.0.330");
|
||||||
|
res.set("CompanyName", "FlashPatch Team");
|
||||||
|
res.set("LegalCopyright", "FlashPatch Team");
|
||||||
|
res.set_manifest(r#"<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="CleanFlashInstaller.app"/>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||||
|
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||||
|
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||||
|
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||||
|
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
</assembly>
|
||||||
|
"#);
|
||||||
|
let _ = res.compile();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
/// Bitflag-based install options, mirroring the C# InstallFlags class.
|
||||||
|
|
||||||
|
pub const NONE: u32 = 0;
|
||||||
|
pub const PEPPER: u32 = 1 << 0;
|
||||||
|
pub const NETSCAPE: u32 = 1 << 1;
|
||||||
|
pub const ACTIVEX: u32 = 1 << 2;
|
||||||
|
pub const PLAYER: u32 = 1 << 3;
|
||||||
|
pub const PLAYER_START_MENU: u32 = 1 << 4;
|
||||||
|
pub const PLAYER_DESKTOP: u32 = 1 << 5;
|
||||||
|
pub const X64: u32 = 1 << 6;
|
||||||
|
pub const DEBUG: u32 = 1 << 7;
|
||||||
|
pub const NATIVE_HOST: u32 = 1 << 8;
|
||||||
|
|
||||||
|
const UNINSTALL_TICKS: u32 = 10;
|
||||||
|
const INSTALL_GENERAL_TICKS: u32 = 5;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct InstallFlags {
|
||||||
|
value: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstallFlags {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { value: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from(value: u32) -> Self {
|
||||||
|
Self { value }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_value(self) -> u32 {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_set(self, flag: u32) -> bool {
|
||||||
|
(self.value & flag) == flag
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_none_set(self) -> bool {
|
||||||
|
self.value == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_flag(&mut self, flag: u32) {
|
||||||
|
self.value |= flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_conditionally(&mut self, condition: bool, flag: u32) {
|
||||||
|
if condition {
|
||||||
|
self.set_flag(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ticks(self) -> u32 {
|
||||||
|
let is_64bit = cfg!(target_pointer_width = "64")
|
||||||
|
|| std::env::var("PROCESSOR_ARCHITEW6432").is_ok();
|
||||||
|
|
||||||
|
let mut ticks = (if self.is_set(PEPPER) { 1 } else { 0 })
|
||||||
|
+ (if self.is_set(NETSCAPE) { 1 } else { 0 })
|
||||||
|
+ (if self.is_set(ACTIVEX) { 2 } else { 0 });
|
||||||
|
|
||||||
|
if is_64bit {
|
||||||
|
ticks *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_set(PLAYER) {
|
||||||
|
ticks += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_set(NATIVE_HOST) {
|
||||||
|
ticks += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ticks += UNINSTALL_TICKS;
|
||||||
|
ticks += INSTALL_GENERAL_TICKS;
|
||||||
|
ticks
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,722 @@
|
|||||||
|
use crate::install_flags::{self, InstallFlags};
|
||||||
|
use crate::installer;
|
||||||
|
use clean_flash_common::{uninstaller, redirection, update_checker, ProgressCallback};
|
||||||
|
use clean_flash_ui::font::FontManager;
|
||||||
|
use clean_flash_ui::renderer::Renderer;
|
||||||
|
use clean_flash_ui::widgets::button::GradientButton;
|
||||||
|
use clean_flash_ui::widgets::checkbox::ImageCheckBox;
|
||||||
|
use clean_flash_ui::widgets::label::Label;
|
||||||
|
use clean_flash_ui::widgets::progress_bar::ProgressBar;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
// Window dimensions matching the C# form.
|
||||||
|
pub const WIDTH: usize = 712;
|
||||||
|
pub const HEIGHT: usize = 329;
|
||||||
|
const BG_COLOR: u32 = Renderer::rgb(50, 51, 51);
|
||||||
|
const FG_COLOR: u32 = Renderer::rgb(245, 245, 245);
|
||||||
|
|
||||||
|
const PANEL_X: i32 = 90;
|
||||||
|
const PANEL_Y: i32 = 162;
|
||||||
|
|
||||||
|
const DISCLAIMER_TEXT: &str = "I am aware that Adobe Flash Player is no longer supported, nor provided by Adobe Inc.\n\
|
||||||
|
Clean Flash Player is a third-party version of Flash Player built from the latest Flash Player version with adware removed.\n\n\
|
||||||
|
Adobe is not required by any means to provide support for this version of Flash Player.";
|
||||||
|
|
||||||
|
const COMPLETE_INSTALL_TEXT: &str = "Clean Flash Player has been successfully installed!\n\
|
||||||
|
Don't forget, Flash Player is no longer compatible with new browsers.\n\n\
|
||||||
|
For browser recommendations and Flash Player updates, check out Clean Flash Player's website!";
|
||||||
|
|
||||||
|
const COMPLETE_INSTALL_WITH_EXTENSION_TEXT: &str = "Clean Flash Player has been successfully installed!\n\n\
|
||||||
|
To use Flash in modern browsers, install the Clean Flash Player extension into your browser.\n\n\
|
||||||
|
For Flash Player updates, check out Clean Flash Player's website!";
|
||||||
|
|
||||||
|
const COMPLETE_UNINSTALL_TEXT: &str = "\nAll versions of Flash Player have been successfully uninstalled.\n\n\
|
||||||
|
If you ever change your mind, check out Clean Flash Player's website!";
|
||||||
|
|
||||||
|
/// Which panel is currently shown in the wizard.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Panel {
|
||||||
|
Disclaimer,
|
||||||
|
Choice,
|
||||||
|
PlayerChoice,
|
||||||
|
DebugChoice,
|
||||||
|
BeforeInstall,
|
||||||
|
Install,
|
||||||
|
Complete,
|
||||||
|
Failure,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shared progress state set by the background install thread.
|
||||||
|
pub struct ProgressState {
|
||||||
|
pub label: String,
|
||||||
|
pub value: i32,
|
||||||
|
pub done: bool,
|
||||||
|
pub error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Full application state for the installer form.
|
||||||
|
pub struct InstallForm {
|
||||||
|
scale: f32,
|
||||||
|
pub panel: Panel,
|
||||||
|
// Header
|
||||||
|
pub title_text: String,
|
||||||
|
pub subtitle_text: String,
|
||||||
|
flash_logo_cache: clean_flash_ui::flash_logo::FlashLogoCache,
|
||||||
|
// Navigation buttons
|
||||||
|
pub prev_button: GradientButton,
|
||||||
|
pub next_button: GradientButton,
|
||||||
|
// Disclaimer panel
|
||||||
|
pub disclaimer_label: Label,
|
||||||
|
pub disclaimer_box: ImageCheckBox,
|
||||||
|
// Choice panel (browser plugins)
|
||||||
|
pub browser_ask_label: Label,
|
||||||
|
pub native_host_box: ImageCheckBox,
|
||||||
|
pub native_host_label: Label,
|
||||||
|
pub pepper_box: ImageCheckBox,
|
||||||
|
pub pepper_label: Label,
|
||||||
|
pub netscape_box: ImageCheckBox,
|
||||||
|
pub netscape_label: Label,
|
||||||
|
pub activex_box: ImageCheckBox,
|
||||||
|
pub activex_label: Label,
|
||||||
|
// Player choice panel
|
||||||
|
pub player_ask_label: Label,
|
||||||
|
pub player_box: ImageCheckBox,
|
||||||
|
pub player_label: Label,
|
||||||
|
pub player_desktop_box: ImageCheckBox,
|
||||||
|
pub player_desktop_label: Label,
|
||||||
|
pub player_start_menu_box: ImageCheckBox,
|
||||||
|
pub player_start_menu_label: Label,
|
||||||
|
// Debug choice panel
|
||||||
|
pub debug_ask_label: Label,
|
||||||
|
pub debug_button: GradientButton,
|
||||||
|
pub debug_chosen: bool,
|
||||||
|
// Before install panel
|
||||||
|
pub before_install_label: Label,
|
||||||
|
// Install panel
|
||||||
|
pub install_header_label: Label,
|
||||||
|
pub progress_label: Label,
|
||||||
|
pub progress_bar: ProgressBar,
|
||||||
|
// Complete panel
|
||||||
|
pub complete_label: Label,
|
||||||
|
// Failure panel
|
||||||
|
pub failure_text_label: Label,
|
||||||
|
pub failure_detail: String,
|
||||||
|
pub copy_error_button: GradientButton,
|
||||||
|
// Shared progress state (for background thread communication).
|
||||||
|
pub progress_state: Arc<Mutex<ProgressState>>,
|
||||||
|
// Fonts loaded once.
|
||||||
|
pub fonts: FontManager,
|
||||||
|
// Mouse tracking
|
||||||
|
prev_mouse_down: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstallForm {
|
||||||
|
pub fn new(scale: f32) -> Self {
|
||||||
|
// Integer coordinate scaler.
|
||||||
|
let s = |v: i32| (v as f32 * scale).round() as i32;
|
||||||
|
// Font size / spacing scaler.
|
||||||
|
let sf = |v: f32| v * scale;
|
||||||
|
// Scaled label helper: font_size is the logical total (base+offset).
|
||||||
|
let lbl = |x: i32, y: i32, text: &str, size: f32| {
|
||||||
|
let mut l = Label::new(s(x), s(y), text, sf(size));
|
||||||
|
l.line_spacing = sf(2.0);
|
||||||
|
l.max_width = sf((WIDTH as i32 - x - 60) as f32);
|
||||||
|
l
|
||||||
|
};
|
||||||
|
// Scaled button helper.
|
||||||
|
let btn = |x: i32, y: i32, w: i32, h: i32, text: &str| GradientButton {
|
||||||
|
font_size: sf(13.0),
|
||||||
|
..GradientButton::new(s(x), s(y), s(w), s(h), text)
|
||||||
|
};
|
||||||
|
// Scaled checkbox helper.
|
||||||
|
let chk = |x: i32, y: i32| ImageCheckBox::with_size(s(x), s(y), s(21));
|
||||||
|
let version = update_checker::FLASH_VERSION;
|
||||||
|
let title_text = "Clean Flash Player".to_string();
|
||||||
|
let subtitle_text = format!("built from version {} (China)", version);
|
||||||
|
|
||||||
|
let fonts = FontManager::new();
|
||||||
|
|
||||||
|
let mut form = Self {
|
||||||
|
scale,
|
||||||
|
panel: Panel::Disclaimer,
|
||||||
|
title_text,
|
||||||
|
subtitle_text,
|
||||||
|
flash_logo_cache: clean_flash_ui::flash_logo::FlashLogoCache::new(),
|
||||||
|
prev_button: btn(90, 286, 138, 31, "QUIT"),
|
||||||
|
next_button: btn(497, 286, 138, 31, "AGREE"),
|
||||||
|
// Disclaimer panel
|
||||||
|
disclaimer_label: lbl(PANEL_X + 25, PANEL_Y, DISCLAIMER_TEXT, 15.0),
|
||||||
|
disclaimer_box: chk(PANEL_X, PANEL_Y),
|
||||||
|
// Choice panel
|
||||||
|
browser_ask_label: lbl(PANEL_X - 2, PANEL_Y + 2, "Which browser plugins would you like to install?", 15.0),
|
||||||
|
native_host_box: chk(PANEL_X, PANEL_Y + 27),
|
||||||
|
native_host_label: lbl(PANEL_X + 24, PANEL_Y + 27, "Modern Browsers (MV3)\n(Chrome/Firefox/Edge)", 15.0),
|
||||||
|
pepper_box: chk(PANEL_X, PANEL_Y + 73),
|
||||||
|
pepper_label: lbl(PANEL_X + 24, PANEL_Y + 73, "Pepper API (PPAPI)\n(Chrome/Opera/Brave)", 15.0),
|
||||||
|
netscape_box: chk(PANEL_X + 186, PANEL_Y + 73),
|
||||||
|
netscape_label: lbl(PANEL_X + 210, PANEL_Y + 73, "Netscape API (NPAPI)\n(Firefox/ESR/Waterfox)", 15.0),
|
||||||
|
activex_box: chk(PANEL_X + 365, PANEL_Y + 73),
|
||||||
|
activex_label: lbl(PANEL_X + 389, PANEL_Y + 73, "ActiveX (OCX)\n(IE/Embedded/Desktop)", 15.0),
|
||||||
|
// Player choice panel
|
||||||
|
player_ask_label: lbl(PANEL_X - 2, PANEL_Y + 2, "Would you like to install the standalone Flash Player?", 15.0),
|
||||||
|
player_box: chk(PANEL_X, PANEL_Y + 47),
|
||||||
|
player_label: lbl(PANEL_X + 24, PANEL_Y + 47, "Install Standalone\nFlash Player", 15.0),
|
||||||
|
player_desktop_box: chk(PANEL_X + 186, PANEL_Y + 47),
|
||||||
|
player_desktop_label: lbl(PANEL_X + 210, PANEL_Y + 47, "Create Shortcuts\non Desktop", 15.0),
|
||||||
|
player_start_menu_box: chk(PANEL_X + 365, PANEL_Y + 47),
|
||||||
|
player_start_menu_label: lbl(PANEL_X + 389, PANEL_Y + 47, "Create Shortcuts\nin Start Menu", 15.0),
|
||||||
|
// Debug choice panel
|
||||||
|
debug_ask_label: lbl(
|
||||||
|
PANEL_X - 2,
|
||||||
|
PANEL_Y + 2,
|
||||||
|
"Would you like to install the debug version of Clean Flash Player?\n\
|
||||||
|
You should only choose the debug version if you are planning to create Flash applications.\n\
|
||||||
|
If you are not sure, simply press NEXT.",
|
||||||
|
15.0,
|
||||||
|
),
|
||||||
|
debug_button: btn(PANEL_X + 186, PANEL_Y + 65, 176, 31, "INSTALL DEBUG VERSION"),
|
||||||
|
debug_chosen: false,
|
||||||
|
// Before install panel
|
||||||
|
before_install_label: lbl(PANEL_X + 3, PANEL_Y + 2, "", 15.0),
|
||||||
|
// Install panel
|
||||||
|
install_header_label: lbl(PANEL_X + 3, PANEL_Y, "Installation in progress...", 15.0),
|
||||||
|
progress_label: lbl(PANEL_X + 46, PANEL_Y + 30, "Preparing...", 15.0),
|
||||||
|
progress_bar: ProgressBar::new(s(PANEL_X + 49), s(PANEL_Y + 58), s(451), s(23)),
|
||||||
|
// Complete panel
|
||||||
|
complete_label: lbl(PANEL_X, PANEL_Y, "", 15.0),
|
||||||
|
// Failure panel
|
||||||
|
failure_text_label: lbl(
|
||||||
|
PANEL_X + 3,
|
||||||
|
PANEL_Y + 2,
|
||||||
|
"Oops! The installation process has encountered an unexpected problem.\n\
|
||||||
|
The following details could be useful. Press the Retry button to try again.",
|
||||||
|
15.0,
|
||||||
|
),
|
||||||
|
failure_detail: String::new(),
|
||||||
|
copy_error_button: btn(PANEL_X + 441, PANEL_Y + 58, 104, 31, "COPY"),
|
||||||
|
progress_state: Arc::new(Mutex::new(ProgressState {
|
||||||
|
label: "Preparing...".into(),
|
||||||
|
value: 0,
|
||||||
|
done: false,
|
||||||
|
error: None,
|
||||||
|
})),
|
||||||
|
fonts,
|
||||||
|
prev_mouse_down: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Modern browser support should be the default choice.
|
||||||
|
form.native_host_box.checked = true;
|
||||||
|
|
||||||
|
// On non-Windows platforms, disable legacy browser plugins and player options.
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
{
|
||||||
|
form.pepper_box.checked = false;
|
||||||
|
form.pepper_box.enabled = false;
|
||||||
|
form.netscape_box.checked = false;
|
||||||
|
form.netscape_box.enabled = false;
|
||||||
|
form.activex_box.checked = false;
|
||||||
|
form.activex_box.enabled = false;
|
||||||
|
form.player_box.checked = false;
|
||||||
|
form.player_box.enabled = false;
|
||||||
|
form.player_desktop_box.checked = false;
|
||||||
|
form.player_desktop_box.enabled = false;
|
||||||
|
form.player_start_menu_box.checked = false;
|
||||||
|
form.player_start_menu_box.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
form
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scale a logical integer coordinate to physical pixels.
|
||||||
|
fn s(&self, v: i32) -> i32 { (v as f32 * self.scale).round() as i32 }
|
||||||
|
/// Scale a logical float value to physical pixels.
|
||||||
|
fn sf(&self, v: f32) -> f32 { v * self.scale }
|
||||||
|
|
||||||
|
/// Called each frame: handle input, update state, draw.
|
||||||
|
pub fn update_and_draw(
|
||||||
|
&mut self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
mx: i32,
|
||||||
|
my: i32,
|
||||||
|
mouse_down: bool,
|
||||||
|
) {
|
||||||
|
let mouse_released = self.prev_mouse_down && !mouse_down;
|
||||||
|
self.prev_mouse_down = mouse_down;
|
||||||
|
|
||||||
|
// Update navigation button hover states.
|
||||||
|
self.prev_button.update(mx, my, mouse_down);
|
||||||
|
self.next_button.update(mx, my, mouse_down);
|
||||||
|
|
||||||
|
// Handle click events.
|
||||||
|
self.handle_input(mx, my, mouse_released);
|
||||||
|
|
||||||
|
// Poll progress state from background thread if installing.
|
||||||
|
if self.panel == Panel::Install {
|
||||||
|
self.poll_progress();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Draw -----
|
||||||
|
renderer.clear(BG_COLOR);
|
||||||
|
|
||||||
|
// Header: flash logo (cached software render).
|
||||||
|
self.flash_logo_cache.draw(
|
||||||
|
renderer, self.s(90), self.s(36), self.s(109), self.s(107),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Title.
|
||||||
|
self.fonts.draw_text(
|
||||||
|
renderer,
|
||||||
|
self.s(233),
|
||||||
|
self.s(54),
|
||||||
|
&self.title_text,
|
||||||
|
self.sf(32.0),
|
||||||
|
FG_COLOR,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Subtitle.
|
||||||
|
self.fonts.draw_text(
|
||||||
|
renderer,
|
||||||
|
self.s(280),
|
||||||
|
self.s(99),
|
||||||
|
&self.subtitle_text,
|
||||||
|
self.sf(17.0),
|
||||||
|
FG_COLOR,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Separator line.
|
||||||
|
renderer.fill_rect(0, self.s(270), renderer.width as i32, self.s(1).max(1), Renderer::rgb(105, 105, 105));
|
||||||
|
|
||||||
|
// Draw current panel.
|
||||||
|
match self.panel {
|
||||||
|
Panel::Disclaimer => self.draw_disclaimer(renderer),
|
||||||
|
Panel::Choice => self.draw_choice(renderer),
|
||||||
|
Panel::PlayerChoice => self.draw_player_choice(renderer),
|
||||||
|
Panel::DebugChoice => self.draw_debug_choice(renderer),
|
||||||
|
Panel::BeforeInstall => self.draw_before_install(renderer),
|
||||||
|
Panel::Install => self.draw_install(renderer),
|
||||||
|
Panel::Complete => self.draw_complete(renderer),
|
||||||
|
Panel::Failure => self.draw_failure(renderer),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigation buttons.
|
||||||
|
self.prev_button.draw(renderer, &self.fonts);
|
||||||
|
self.next_button.draw(renderer, &self.fonts);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_input(&mut self, mx: i32, my: i32, mouse_released: bool) {
|
||||||
|
// Navigation button clicks.
|
||||||
|
if self.prev_button.clicked(mx, my, mouse_released) {
|
||||||
|
self.on_prev_clicked();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.next_button.clicked(mx, my, mouse_released) {
|
||||||
|
self.on_next_clicked();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panel-specific input.
|
||||||
|
match self.panel {
|
||||||
|
Panel::Disclaimer => {
|
||||||
|
let toggled = self.disclaimer_box.toggle_if_clicked(mx, my, mouse_released);
|
||||||
|
if toggled || self.disclaimer_label.clicked(mx, my, mouse_released, &self.fonts) {
|
||||||
|
if !toggled {
|
||||||
|
self.disclaimer_box.checked = !self.disclaimer_box.checked;
|
||||||
|
}
|
||||||
|
self.next_button.enabled = self.disclaimer_box.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Panel::Choice => {
|
||||||
|
self.native_host_box.toggle_if_clicked(mx, my, mouse_released);
|
||||||
|
self.pepper_box.toggle_if_clicked(mx, my, mouse_released);
|
||||||
|
self.netscape_box.toggle_if_clicked(mx, my, mouse_released);
|
||||||
|
self.activex_box.toggle_if_clicked(mx, my, mouse_released);
|
||||||
|
if self.native_host_box.enabled
|
||||||
|
&& self
|
||||||
|
.native_host_label
|
||||||
|
.clicked(mx, my, mouse_released, &self.fonts)
|
||||||
|
{
|
||||||
|
self.native_host_box.checked = !self.native_host_box.checked;
|
||||||
|
}
|
||||||
|
if self.pepper_box.enabled
|
||||||
|
&& self.pepper_label.clicked(mx, my, mouse_released, &self.fonts)
|
||||||
|
{
|
||||||
|
self.pepper_box.checked = !self.pepper_box.checked;
|
||||||
|
}
|
||||||
|
if self.netscape_box.enabled
|
||||||
|
&& self.netscape_label.clicked(mx, my, mouse_released, &self.fonts)
|
||||||
|
{
|
||||||
|
self.netscape_box.checked = !self.netscape_box.checked;
|
||||||
|
}
|
||||||
|
if self.activex_box.enabled
|
||||||
|
&& self.activex_label.clicked(mx, my, mouse_released, &self.fonts)
|
||||||
|
{
|
||||||
|
self.activex_box.checked = !self.activex_box.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Panel::PlayerChoice => {
|
||||||
|
self.player_box.toggle_if_clicked(mx, my, mouse_released);
|
||||||
|
self.player_desktop_box.toggle_if_clicked(mx, my, mouse_released);
|
||||||
|
self.player_start_menu_box.toggle_if_clicked(mx, my, mouse_released);
|
||||||
|
if self.player_box.enabled
|
||||||
|
&& self.player_label.clicked(mx, my, mouse_released, &self.fonts)
|
||||||
|
{
|
||||||
|
self.player_box.checked = !self.player_box.checked;
|
||||||
|
}
|
||||||
|
if self.player_desktop_box.enabled
|
||||||
|
&& self
|
||||||
|
.player_desktop_label
|
||||||
|
.clicked(mx, my, mouse_released, &self.fonts)
|
||||||
|
&& self.player_box.checked
|
||||||
|
{
|
||||||
|
self.player_desktop_box.checked = !self.player_desktop_box.checked;
|
||||||
|
}
|
||||||
|
if self.player_start_menu_box.enabled
|
||||||
|
&& self
|
||||||
|
.player_start_menu_label
|
||||||
|
.clicked(mx, my, mouse_released, &self.fonts)
|
||||||
|
&& self.player_box.checked
|
||||||
|
{
|
||||||
|
self.player_start_menu_box.checked = !self.player_start_menu_box.checked;
|
||||||
|
}
|
||||||
|
// Disable sub-options when player unchecked.
|
||||||
|
self.player_desktop_box.enabled = self.player_box.checked;
|
||||||
|
self.player_start_menu_box.enabled = self.player_box.checked;
|
||||||
|
if !self.player_box.checked {
|
||||||
|
self.player_desktop_box.checked = false;
|
||||||
|
self.player_start_menu_box.checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Panel::DebugChoice => {
|
||||||
|
if self.debug_button.clicked(mx, my, mouse_released) {
|
||||||
|
// In the C# app this shows a MessageBox. For the Rust port we toggle directly.
|
||||||
|
self.debug_chosen = true;
|
||||||
|
self.open_before_install();
|
||||||
|
}
|
||||||
|
self.debug_button.update(mx, my, self.prev_mouse_down);
|
||||||
|
}
|
||||||
|
Panel::Failure => {
|
||||||
|
self.copy_error_button.update(mx, my, self.prev_mouse_down);
|
||||||
|
if self.copy_error_button.clicked(mx, my, mouse_released) {
|
||||||
|
// Copy error to clipboard via clip.exe.
|
||||||
|
let _ = std::process::Command::new("cmd")
|
||||||
|
.args(["/C", &format!("echo {} | clip", self.failure_detail)])
|
||||||
|
.output();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_prev_clicked(&mut self) {
|
||||||
|
match self.panel {
|
||||||
|
Panel::Disclaimer | Panel::Complete | Panel::Failure => {
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
Panel::Choice => self.open_disclaimer(),
|
||||||
|
Panel::PlayerChoice => self.open_choice(),
|
||||||
|
Panel::DebugChoice => self.open_player_choice(),
|
||||||
|
Panel::BeforeInstall => {
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
self.open_debug_choice();
|
||||||
|
} else {
|
||||||
|
self.open_choice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_next_clicked(&mut self) {
|
||||||
|
match self.panel {
|
||||||
|
Panel::Disclaimer => self.open_choice(),
|
||||||
|
Panel::Choice => {
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
self.open_player_choice();
|
||||||
|
} else {
|
||||||
|
self.open_before_install();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Panel::PlayerChoice => self.open_debug_choice(),
|
||||||
|
Panel::DebugChoice => self.open_before_install(),
|
||||||
|
Panel::BeforeInstall | Panel::Failure => self.open_install(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_disclaimer(&mut self) {
|
||||||
|
self.panel = Panel::Disclaimer;
|
||||||
|
self.prev_button.text = "QUIT".into();
|
||||||
|
self.next_button.text = "AGREE".into();
|
||||||
|
self.next_button.visible = true;
|
||||||
|
self.next_button.enabled = self.disclaimer_box.checked;
|
||||||
|
self.prev_button.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_choice(&mut self) {
|
||||||
|
self.panel = Panel::Choice;
|
||||||
|
self.prev_button.text = "BACK".into();
|
||||||
|
self.next_button.text = "NEXT".into();
|
||||||
|
self.next_button.visible = true;
|
||||||
|
self.next_button.enabled = true;
|
||||||
|
self.prev_button.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_player_choice(&mut self) {
|
||||||
|
self.panel = Panel::PlayerChoice;
|
||||||
|
self.prev_button.text = "BACK".into();
|
||||||
|
self.next_button.text = "NEXT".into();
|
||||||
|
self.next_button.visible = true;
|
||||||
|
self.next_button.enabled = true;
|
||||||
|
self.prev_button.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_debug_choice(&mut self) {
|
||||||
|
self.panel = Panel::DebugChoice;
|
||||||
|
self.debug_chosen = false;
|
||||||
|
self.prev_button.text = "BACK".into();
|
||||||
|
self.next_button.text = "NEXT".into();
|
||||||
|
self.next_button.visible = true;
|
||||||
|
self.next_button.enabled = true;
|
||||||
|
self.prev_button.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_before_install(&mut self) {
|
||||||
|
self.panel = Panel::BeforeInstall;
|
||||||
|
self.prev_button.text = "BACK".into();
|
||||||
|
self.prev_button.enabled = true;
|
||||||
|
|
||||||
|
let has_plugins =
|
||||||
|
self.native_host_box.checked || self.pepper_box.checked || self.netscape_box.checked || self.activex_box.checked || self.player_box.checked;
|
||||||
|
|
||||||
|
if has_plugins {
|
||||||
|
let mut browsers = Vec::new();
|
||||||
|
if self.native_host_box.checked {
|
||||||
|
browsers.push("Modern Browsers (via extension)");
|
||||||
|
}
|
||||||
|
if self.pepper_box.checked {
|
||||||
|
browsers.push("Google Chrome");
|
||||||
|
}
|
||||||
|
if self.netscape_box.checked {
|
||||||
|
browsers.push("Mozilla Firefox");
|
||||||
|
}
|
||||||
|
if self.activex_box.checked {
|
||||||
|
browsers.push("Internet Explorer");
|
||||||
|
}
|
||||||
|
|
||||||
|
let browser_str = join_with_and(&browsers);
|
||||||
|
self.before_install_label.text = format!(
|
||||||
|
"You are about to install Clean Flash Player.\n\
|
||||||
|
Please close any browser windows running Flash content before you continue.\n\n\
|
||||||
|
The installer will close all browser windows running Flash, uninstall previous versions of Flash Player and Flash Center, and install Flash for {}.",
|
||||||
|
browser_str
|
||||||
|
);
|
||||||
|
self.next_button.text = "INSTALL".into();
|
||||||
|
} else {
|
||||||
|
self.before_install_label.text =
|
||||||
|
"You are about to uninstall Clean Flash Player.\n\
|
||||||
|
Please close any browser windows running Flash content before you continue.\n\n\
|
||||||
|
The installer will completely remove all versions of Flash Player from this computer, including Clean Flash Player and older versions of Adobe Flash Player."
|
||||||
|
.to_string();
|
||||||
|
self.next_button.text = "UNINSTALL".into();
|
||||||
|
}
|
||||||
|
self.next_button.visible = true;
|
||||||
|
self.next_button.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_install(&mut self) {
|
||||||
|
self.panel = Panel::Install;
|
||||||
|
self.prev_button.enabled = false;
|
||||||
|
self.next_button.visible = false;
|
||||||
|
|
||||||
|
let mut flags = InstallFlags::new();
|
||||||
|
flags.set_conditionally(self.native_host_box.checked, install_flags::NATIVE_HOST);
|
||||||
|
flags.set_conditionally(self.pepper_box.checked, install_flags::PEPPER);
|
||||||
|
flags.set_conditionally(self.netscape_box.checked, install_flags::NETSCAPE);
|
||||||
|
flags.set_conditionally(self.activex_box.checked, install_flags::ACTIVEX);
|
||||||
|
flags.set_conditionally(self.player_box.checked, install_flags::PLAYER);
|
||||||
|
flags.set_conditionally(self.player_desktop_box.checked, install_flags::PLAYER_DESKTOP);
|
||||||
|
flags.set_conditionally(
|
||||||
|
self.player_start_menu_box.checked,
|
||||||
|
install_flags::PLAYER_START_MENU,
|
||||||
|
);
|
||||||
|
flags.set_conditionally(self.debug_chosen, install_flags::DEBUG);
|
||||||
|
|
||||||
|
self.progress_bar.maximum = flags.get_ticks() as i32;
|
||||||
|
self.progress_bar.value = 0;
|
||||||
|
|
||||||
|
// Reset shared state.
|
||||||
|
{
|
||||||
|
let mut state = self.progress_state.lock().unwrap();
|
||||||
|
state.label = "Preparing...".into();
|
||||||
|
state.value = 0;
|
||||||
|
state.done = false;
|
||||||
|
state.error = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn background thread.
|
||||||
|
let progress = Arc::clone(&self.progress_state);
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let callback = ThreadProgressCallback {
|
||||||
|
state: Arc::clone(&progress),
|
||||||
|
};
|
||||||
|
|
||||||
|
let redir = redirection::disable_redirection();
|
||||||
|
|
||||||
|
let result = (|| -> Result<(), clean_flash_common::InstallError> {
|
||||||
|
uninstaller::uninstall(&callback)?;
|
||||||
|
installer::install(&callback, &mut flags)?;
|
||||||
|
Ok(())
|
||||||
|
})();
|
||||||
|
|
||||||
|
redirection::enable_redirection(redir);
|
||||||
|
|
||||||
|
let mut state = progress.lock().unwrap();
|
||||||
|
if let Err(e) = result {
|
||||||
|
state.error = Some(e.to_string());
|
||||||
|
}
|
||||||
|
state.done = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_progress(&mut self) {
|
||||||
|
let state = self.progress_state.lock().unwrap();
|
||||||
|
self.progress_label.text = state.label.clone();
|
||||||
|
self.progress_bar.value = state.value;
|
||||||
|
|
||||||
|
if state.done {
|
||||||
|
if let Some(ref err) = state.error {
|
||||||
|
self.failure_detail = err.clone();
|
||||||
|
drop(state);
|
||||||
|
self.open_failure();
|
||||||
|
} else {
|
||||||
|
drop(state);
|
||||||
|
self.open_complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_complete(&mut self) {
|
||||||
|
self.panel = Panel::Complete;
|
||||||
|
self.prev_button.text = "QUIT".into();
|
||||||
|
self.prev_button.enabled = true;
|
||||||
|
self.next_button.visible = false;
|
||||||
|
|
||||||
|
if self.native_host_box.checked {
|
||||||
|
self.complete_label.text = COMPLETE_INSTALL_WITH_EXTENSION_TEXT.to_string();
|
||||||
|
} else if self.pepper_box.checked || self.netscape_box.checked || self.activex_box.checked {
|
||||||
|
self.complete_label.text = COMPLETE_INSTALL_TEXT.to_string();
|
||||||
|
} else {
|
||||||
|
self.complete_label.text = COMPLETE_UNINSTALL_TEXT.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_failure(&mut self) {
|
||||||
|
self.panel = Panel::Failure;
|
||||||
|
self.prev_button.text = "QUIT".into();
|
||||||
|
self.prev_button.enabled = true;
|
||||||
|
self.next_button.text = "RETRY".into();
|
||||||
|
self.next_button.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Drawing helpers ----
|
||||||
|
|
||||||
|
fn draw_disclaimer(&self, r: &mut Renderer) {
|
||||||
|
self.disclaimer_box.draw(r);
|
||||||
|
self.disclaimer_label.draw(r, &self.fonts);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_choice(&self, r: &mut Renderer) {
|
||||||
|
self.browser_ask_label.draw(r, &self.fonts);
|
||||||
|
self.native_host_box.draw(r);
|
||||||
|
self.native_host_label.draw(r, &self.fonts);
|
||||||
|
self.pepper_box.draw(r);
|
||||||
|
self.pepper_label.draw(r, &self.fonts);
|
||||||
|
self.netscape_box.draw(r);
|
||||||
|
self.netscape_label.draw(r, &self.fonts);
|
||||||
|
self.activex_box.draw(r);
|
||||||
|
self.activex_label.draw(r, &self.fonts);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_player_choice(&self, r: &mut Renderer) {
|
||||||
|
self.player_ask_label.draw(r, &self.fonts);
|
||||||
|
self.player_box.draw(r);
|
||||||
|
self.player_label.draw(r, &self.fonts);
|
||||||
|
self.player_desktop_box.draw(r);
|
||||||
|
self.player_desktop_label.draw(r, &self.fonts);
|
||||||
|
self.player_start_menu_box.draw(r);
|
||||||
|
self.player_start_menu_label.draw(r, &self.fonts);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_debug_choice(&mut self, r: &mut Renderer) {
|
||||||
|
self.debug_ask_label.draw(r, &self.fonts);
|
||||||
|
self.debug_button.draw(r, &self.fonts);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_before_install(&self, r: &mut Renderer) {
|
||||||
|
self.before_install_label.draw(r, &self.fonts);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_install(&self, r: &mut Renderer) {
|
||||||
|
self.install_header_label.draw(r, &self.fonts);
|
||||||
|
self.progress_label.draw(r, &self.fonts);
|
||||||
|
self.progress_bar.draw(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_complete(&self, r: &mut Renderer) {
|
||||||
|
self.complete_label.draw(r, &self.fonts);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_failure(&self, r: &mut Renderer) {
|
||||||
|
self.failure_text_label.draw(r, &self.fonts);
|
||||||
|
// Draw error detail as clipped text.
|
||||||
|
let detail_text = if self.failure_detail.len() > 300 {
|
||||||
|
&self.failure_detail[..300]
|
||||||
|
} else {
|
||||||
|
&self.failure_detail
|
||||||
|
};
|
||||||
|
self.fonts.draw_text_multiline(
|
||||||
|
r,
|
||||||
|
self.s(PANEL_X + 4),
|
||||||
|
self.s(PANEL_Y + 44),
|
||||||
|
detail_text,
|
||||||
|
self.sf(11.0),
|
||||||
|
FG_COLOR,
|
||||||
|
self.sf(1.0),
|
||||||
|
self.sf((WIDTH as i32 - PANEL_X - 14) as f32),
|
||||||
|
);
|
||||||
|
self.copy_error_button.draw(r, &self.fonts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Progress callback that writes to the shared state from the background thread.
|
||||||
|
struct ThreadProgressCallback {
|
||||||
|
state: Arc<Mutex<ProgressState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgressCallback for ThreadProgressCallback {
|
||||||
|
fn update_progress_label(&self, text: &str, tick: bool) {
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
state.label = text.to_string();
|
||||||
|
if tick {
|
||||||
|
state.value += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick_progress(&self) {
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
state.value += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn join_with_and(items: &[&str]) -> String {
|
||||||
|
match items.len() {
|
||||||
|
0 => String::new(),
|
||||||
|
1 => items[0].to_string(),
|
||||||
|
2 => format!("{} and {}", items[0], items[1]),
|
||||||
|
_ => {
|
||||||
|
let (last, rest) = items.split_last().unwrap();
|
||||||
|
format!("{} and {}", rest.join(", "), last)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,427 @@
|
|||||||
|
use crate::install_flags::{self, InstallFlags};
|
||||||
|
use clean_flash_common::{
|
||||||
|
native_host, process_utils, registry, resources, system_info, InstallError, ProgressCallback,
|
||||||
|
};
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::{self, Cursor};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// Metadata for a single installable component.
|
||||||
|
struct InstallEntry {
|
||||||
|
install_text: &'static str,
|
||||||
|
required_flags: u32,
|
||||||
|
target_directory: PathBuf,
|
||||||
|
registry_instructions: Option<&'static str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register an ActiveX OCX via regsvr32 (unregister first, then register).
|
||||||
|
pub fn register_activex(filename: &str) -> Result<(), InstallError> {
|
||||||
|
let path = Path::new(filename);
|
||||||
|
let dir = path.parent().unwrap_or(Path::new("."));
|
||||||
|
let file_name = path
|
||||||
|
.file_name()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let _ = env::set_current_dir(dir);
|
||||||
|
|
||||||
|
let process = process_utils::run_process("regsvr32.exe", &["/s", "/u", &file_name]);
|
||||||
|
if !process.is_successful() {
|
||||||
|
return Err(InstallError::new(format!(
|
||||||
|
"Failed to unregister ActiveX plugin: error code {}\n\n{}",
|
||||||
|
process.exit_code, process.output
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let process = process_utils::run_process("regsvr32.exe", &["/s", &file_name]);
|
||||||
|
if !process.is_successful() {
|
||||||
|
return Err(InstallError::new(format!(
|
||||||
|
"Failed to register ActiveX plugin: error code {}\n\n{}",
|
||||||
|
process.exit_code, process.output
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a Windows shortcut (.lnk) using a PowerShell one-liner.
|
||||||
|
fn create_shortcut(
|
||||||
|
folder: &Path,
|
||||||
|
executable: &Path,
|
||||||
|
name: &str,
|
||||||
|
description: &str,
|
||||||
|
) -> Result<(), InstallError> {
|
||||||
|
let lnk_path = folder.join(format!("{}.lnk", name));
|
||||||
|
let exe_str = executable.to_string_lossy();
|
||||||
|
let lnk_str = lnk_path.to_string_lossy();
|
||||||
|
|
||||||
|
// Use PowerShell to create the shortcut via WScript.Shell COM.
|
||||||
|
let script = format!(
|
||||||
|
"$ws = New-Object -ComObject WScript.Shell; \
|
||||||
|
$s = $ws.CreateShortcut('{}'); \
|
||||||
|
$s.TargetPath = '{}'; \
|
||||||
|
$s.Description = '{}'; \
|
||||||
|
$s.IconLocation = '{}'; \
|
||||||
|
$s.Save()",
|
||||||
|
lnk_str, exe_str, description, exe_str
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = process_utils::run_process("powershell.exe", &["-NoProfile", "-Command", &script]);
|
||||||
|
if !result.is_successful() {
|
||||||
|
return Err(InstallError::new(format!(
|
||||||
|
"Failed to create shortcut: {}",
|
||||||
|
result.output
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the embedded 7z archive and install files to the correct locations.
|
||||||
|
fn install_from_archive(
|
||||||
|
archive_bytes: &[u8],
|
||||||
|
form: &dyn ProgressCallback,
|
||||||
|
flags: &mut InstallFlags,
|
||||||
|
) -> Result<(), InstallError> {
|
||||||
|
let si = system_info::SystemInfo::new();
|
||||||
|
let flash32_path = si.flash32_path.clone();
|
||||||
|
let flash64_path = si.flash64_path.clone();
|
||||||
|
let system32_path = si.system32_path.clone();
|
||||||
|
let flash_program32_path = si.flash_program32_path.clone();
|
||||||
|
let native_host_dir = native_host::get_native_host_install_dir();
|
||||||
|
|
||||||
|
let mut registry_to_apply: Vec<&str> = vec![resources::INSTALL_GENERAL];
|
||||||
|
|
||||||
|
if si.is_64bit {
|
||||||
|
flags.set_flag(install_flags::X64);
|
||||||
|
registry_to_apply.push(resources::INSTALL_GENERAL_64);
|
||||||
|
}
|
||||||
|
|
||||||
|
let entries: Vec<(&str, InstallEntry)> = vec![
|
||||||
|
(
|
||||||
|
"controlpanel",
|
||||||
|
InstallEntry {
|
||||||
|
install_text: "Installing Flash Player utilities...",
|
||||||
|
required_flags: install_flags::NONE,
|
||||||
|
target_directory: system32_path.clone(),
|
||||||
|
registry_instructions: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"uninstaller",
|
||||||
|
InstallEntry {
|
||||||
|
install_text: "Extracting uninstaller...",
|
||||||
|
required_flags: install_flags::NONE,
|
||||||
|
target_directory: flash_program32_path.clone(),
|
||||||
|
registry_instructions: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"standalone",
|
||||||
|
InstallEntry {
|
||||||
|
install_text: "Installing 32-bit Standalone Flash Player...",
|
||||||
|
required_flags: install_flags::PLAYER,
|
||||||
|
target_directory: flash_program32_path.clone(),
|
||||||
|
registry_instructions: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ocx32",
|
||||||
|
InstallEntry {
|
||||||
|
install_text: "Installing 32-bit Flash Player for Internet Explorer...",
|
||||||
|
required_flags: install_flags::ACTIVEX,
|
||||||
|
target_directory: flash32_path.clone(),
|
||||||
|
registry_instructions: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"np32",
|
||||||
|
InstallEntry {
|
||||||
|
install_text: "Installing 32-bit Flash Player for Firefox...",
|
||||||
|
required_flags: install_flags::NETSCAPE,
|
||||||
|
target_directory: flash32_path.clone(),
|
||||||
|
registry_instructions: Some(resources::INSTALL_NP),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pp32",
|
||||||
|
InstallEntry {
|
||||||
|
install_text: "Installing 32-bit Flash Player for Chrome...",
|
||||||
|
required_flags: install_flags::PEPPER,
|
||||||
|
target_directory: flash32_path.clone(),
|
||||||
|
registry_instructions: Some(resources::INSTALL_PP),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ocx64",
|
||||||
|
InstallEntry {
|
||||||
|
install_text: "Installing 64-bit Flash Player for Internet Explorer...",
|
||||||
|
required_flags: install_flags::ACTIVEX | install_flags::X64,
|
||||||
|
target_directory: flash64_path.clone(),
|
||||||
|
registry_instructions: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"np64",
|
||||||
|
InstallEntry {
|
||||||
|
install_text: "Installing 64-bit Flash Player for Firefox...",
|
||||||
|
required_flags: install_flags::NETSCAPE | install_flags::X64,
|
||||||
|
target_directory: flash64_path.clone(),
|
||||||
|
registry_instructions: Some(resources::INSTALL_NP_64),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pp64",
|
||||||
|
InstallEntry {
|
||||||
|
install_text: "Installing 64-bit Flash Player for Chrome...",
|
||||||
|
required_flags: install_flags::PEPPER | install_flags::X64,
|
||||||
|
target_directory: flash64_path.clone(),
|
||||||
|
registry_instructions: Some(resources::INSTALL_PP_64),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"native-host-64",
|
||||||
|
InstallEntry {
|
||||||
|
install_text: "Installing native messaging host (64-bit)...",
|
||||||
|
required_flags: install_flags::NATIVE_HOST | install_flags::X64,
|
||||||
|
target_directory: native_host_dir.clone(),
|
||||||
|
registry_instructions: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"native-host-32",
|
||||||
|
InstallEntry {
|
||||||
|
install_text: "Installing native messaging host (32-bit)...",
|
||||||
|
required_flags: install_flags::NATIVE_HOST,
|
||||||
|
target_directory: native_host_dir.clone(),
|
||||||
|
registry_instructions: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let legacy = si.is_legacy_windows();
|
||||||
|
|
||||||
|
// Extract archive using sevenz-rust2.
|
||||||
|
sevenz_rust2::decompress_with_extract_fn(
|
||||||
|
Cursor::new(archive_bytes),
|
||||||
|
".",
|
||||||
|
|entry, reader, _dest| {
|
||||||
|
// Skip directory entries — they have no file content.
|
||||||
|
if entry.is_directory() {
|
||||||
|
io::copy(reader, &mut io::sink()).map_err(sevenz_rust2::Error::from)?;
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry_name = entry.name().to_string();
|
||||||
|
let parts: Vec<&str> = entry_name.split('/').collect();
|
||||||
|
if parts.is_empty() {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let filename = parts[0];
|
||||||
|
let install_key = filename.split('-').next().unwrap_or(filename);
|
||||||
|
let is_pp32 = install_key == "pp32";
|
||||||
|
let is_pp64 = install_key == "pp64";
|
||||||
|
let should_extract_pepper_for_native_host =
|
||||||
|
flags.is_set(install_flags::NATIVE_HOST)
|
||||||
|
&& ((si.is_64bit && is_pp64) || (!si.is_64bit && is_pp32));
|
||||||
|
|
||||||
|
// Find the matching entry: try exact match on full dirname first,
|
||||||
|
// then fall back to first segment before '-'.
|
||||||
|
let Some((_key, install_entry)) = entries
|
||||||
|
.iter()
|
||||||
|
.find(|(k, _)| *k == filename)
|
||||||
|
.or_else(|| entries.iter().find(|(k, _)| *k == install_key))
|
||||||
|
else {
|
||||||
|
io::copy(reader, &mut io::sink()).map_err(sevenz_rust2::Error::from)?;
|
||||||
|
return Ok(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check required flags.
|
||||||
|
let should_install_by_flags = install_entry.required_flags == install_flags::NONE
|
||||||
|
|| flags.is_set(install_entry.required_flags);
|
||||||
|
if install_entry.required_flags != install_flags::NONE
|
||||||
|
&& !flags.is_set(install_entry.required_flags)
|
||||||
|
&& !(should_extract_pepper_for_native_host && (is_pp32 || is_pp64))
|
||||||
|
{
|
||||||
|
io::copy(reader, &mut io::sink()).map_err(sevenz_rust2::Error::from)?;
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For native-host: on 64-bit use only native-host-64, on 32-bit use only native-host-32.
|
||||||
|
if filename == "native-host-32" && si.is_64bit {
|
||||||
|
io::copy(reader, &mut io::sink()).map_err(sevenz_rust2::Error::from)?;
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
if filename == "native-host-64" && !si.is_64bit {
|
||||||
|
io::copy(reader, &mut io::sink()).map_err(sevenz_rust2::Error::from)?;
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check debug flag match (skip native-host entries from this check).
|
||||||
|
if install_entry.required_flags != install_flags::NONE
|
||||||
|
&& (install_entry.required_flags & install_flags::NATIVE_HOST) == 0
|
||||||
|
{
|
||||||
|
let is_debug_file = filename.contains("-debug");
|
||||||
|
if flags.is_set(install_flags::DEBUG) != is_debug_file {
|
||||||
|
io::copy(reader, &mut io::sink()).map_err(sevenz_rust2::Error::from)?;
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check legacy flag for ActiveX entries.
|
||||||
|
if (install_entry.required_flags & install_flags::ACTIVEX) != 0 {
|
||||||
|
let is_legacy_file = filename.contains("-legacy");
|
||||||
|
if legacy != is_legacy_file {
|
||||||
|
io::copy(reader, &mut io::sink()).map_err(sevenz_rust2::Error::from)?;
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let extract_for_native_host_only = !should_install_by_flags
|
||||||
|
&& should_extract_pepper_for_native_host
|
||||||
|
&& (is_pp32 || is_pp64);
|
||||||
|
|
||||||
|
if extract_for_native_host_only {
|
||||||
|
form.update_progress_label(
|
||||||
|
"Extracting Pepper files required by modern browser support...",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
form.update_progress_label(install_entry.install_text, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_directory = if extract_for_native_host_only {
|
||||||
|
native_host_dir.clone()
|
||||||
|
} else {
|
||||||
|
install_entry.target_directory.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure target directory exists.
|
||||||
|
let _ = fs::create_dir_all(&target_directory);
|
||||||
|
|
||||||
|
// Extract file: use just the file name (strip any path prefix).
|
||||||
|
let out_name = parts.last().unwrap_or(&filename);
|
||||||
|
let out_path = target_directory.join(out_name);
|
||||||
|
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
reader.read_to_end(&mut buf).map_err(sevenz_rust2::Error::from)?;
|
||||||
|
fs::write(&out_path, &buf).map_err(sevenz_rust2::Error::from)?;
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(|e| InstallError::new(format!("Failed to extract archive: {}", e)))?;
|
||||||
|
|
||||||
|
// Create Player shortcuts.
|
||||||
|
if flags.is_set(install_flags::PLAYER) {
|
||||||
|
let is_debug = flags.is_set(install_flags::DEBUG);
|
||||||
|
let name = if is_debug {
|
||||||
|
"Flash Player (Debug)"
|
||||||
|
} else {
|
||||||
|
"Flash Player"
|
||||||
|
};
|
||||||
|
let description = format!(
|
||||||
|
"Standalone Flash Player {}{}",
|
||||||
|
clean_flash_common::update_checker::FLASH_VERSION,
|
||||||
|
if is_debug { " (Debug)" } else { "" }
|
||||||
|
);
|
||||||
|
let exe_name = if is_debug {
|
||||||
|
"flashplayer_sa_debug.exe"
|
||||||
|
} else {
|
||||||
|
"flashplayer_sa.exe"
|
||||||
|
};
|
||||||
|
let executable = flash_program32_path.join(exe_name);
|
||||||
|
|
||||||
|
if flags.is_set(install_flags::PLAYER_START_MENU) {
|
||||||
|
if let Some(start_menu) = get_start_menu() {
|
||||||
|
let _ = create_shortcut(&start_menu, &executable, name, &description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.is_set(install_flags::PLAYER_DESKTOP) {
|
||||||
|
if let Some(desktop) = get_desktop() {
|
||||||
|
let _ = create_shortcut(&desktop, &executable, name, &description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect registry entries for enabled components.
|
||||||
|
for (_key, entry) in &entries {
|
||||||
|
if flags.is_set(entry.required_flags) {
|
||||||
|
if let Some(reg) = entry.registry_instructions {
|
||||||
|
registry_to_apply.push(reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form.update_progress_label("Applying registry changes...", true);
|
||||||
|
let refs: Vec<&str> = registry_to_apply.iter().copied().collect();
|
||||||
|
registry::apply_registry(&refs)?;
|
||||||
|
|
||||||
|
// Register ActiveX OCX files.
|
||||||
|
if flags.is_set(install_flags::ACTIVEX) {
|
||||||
|
form.update_progress_label(
|
||||||
|
"Activating 32-bit Flash Player for Internet Explorer...",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
let ocx32 = flash32_path.join(format!("Flash32_{}.ocx", si.version_path));
|
||||||
|
register_activex(&ocx32.to_string_lossy())?;
|
||||||
|
|
||||||
|
if si.is_64bit {
|
||||||
|
form.update_progress_label(
|
||||||
|
"Activating 64-bit Flash Player for Internet Explorer...",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
let ocx64 = flash64_path.join(format!("Flash64_{}.ocx", si.version_path));
|
||||||
|
register_activex(&ocx64.to_string_lossy())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install native messaging host manifests for detected browsers.
|
||||||
|
if flags.is_set(install_flags::NATIVE_HOST) {
|
||||||
|
native_host::install_native_host(form)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main install entry point.
|
||||||
|
pub fn install(
|
||||||
|
form: &dyn ProgressCallback,
|
||||||
|
flags: &mut InstallFlags,
|
||||||
|
) -> Result<(), InstallError> {
|
||||||
|
if flags.is_none_set() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 7z archive is embedded in the binary via include_bytes!.
|
||||||
|
// For the port, we expect it at a known resource path; if not present,
|
||||||
|
// this is a no-op placeholder.
|
||||||
|
let archive_bytes: &[u8] = include_bytes!("../cleanflash.7z");
|
||||||
|
|
||||||
|
if archive_bytes.is_empty() {
|
||||||
|
// Nothing to extract; still apply the rest of the steps.
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
install_from_archive(archive_bytes, form, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_start_menu() -> Option<PathBuf> {
|
||||||
|
env::var("APPDATA")
|
||||||
|
.ok()
|
||||||
|
.map(|p| {
|
||||||
|
PathBuf::from(p)
|
||||||
|
.join("Microsoft")
|
||||||
|
.join("Windows")
|
||||||
|
.join("Start Menu")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_desktop() -> Option<PathBuf> {
|
||||||
|
env::var("USERPROFILE")
|
||||||
|
.ok()
|
||||||
|
.map(|p| PathBuf::from(p).join("Desktop"))
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
#![windows_subsystem = "windows"]
|
||||||
|
|
||||||
|
mod install_flags;
|
||||||
|
mod install_form;
|
||||||
|
mod installer;
|
||||||
|
|
||||||
|
use install_form::{InstallForm, HEIGHT, WIDTH};
|
||||||
|
use clean_flash_ui::renderer::Renderer;
|
||||||
|
use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let title = format!(
|
||||||
|
"Clean Flash Player {} Installer",
|
||||||
|
clean_flash_common::update_checker::FLASH_VERSION
|
||||||
|
);
|
||||||
|
|
||||||
|
// Query DPI before creating any window. The manifest already marks the process
|
||||||
|
// as per-monitor v2 DPI aware, so GetDpiForSystem returns the real hardware DPI.
|
||||||
|
let scale = clean_flash_ui::get_dpi_scale();
|
||||||
|
let phys_w = (WIDTH as f32 * scale).round() as usize;
|
||||||
|
let phys_h = (HEIGHT as f32 * scale).round() as usize;
|
||||||
|
|
||||||
|
let mut window = Window::new(
|
||||||
|
&title,
|
||||||
|
phys_w,
|
||||||
|
phys_h,
|
||||||
|
WindowOptions {
|
||||||
|
resize: false,
|
||||||
|
..WindowOptions::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("Failed to create window");
|
||||||
|
|
||||||
|
// Set window icon from the resource embedded by build.rs.
|
||||||
|
clean_flash_ui::set_window_icon(&window);
|
||||||
|
|
||||||
|
// Cap at ~24 fps.
|
||||||
|
window.set_target_fps(24);
|
||||||
|
|
||||||
|
// Renderer operates at physical resolution; the form layout is scaled accordingly.
|
||||||
|
let mut renderer = Renderer::new(phys_w, phys_h);
|
||||||
|
let mut form = InstallForm::new(scale);
|
||||||
|
|
||||||
|
while window.is_open() && !window.is_key_down(Key::Escape)
|
||||||
|
&& !(window.is_key_down(Key::F4)
|
||||||
|
&& (window.is_key_down(Key::LeftAlt) || window.is_key_down(Key::RightAlt)))
|
||||||
|
{
|
||||||
|
let (mx, my) = window
|
||||||
|
.get_mouse_pos(MouseMode::Clamp)
|
||||||
|
.unwrap_or((0.0, 0.0));
|
||||||
|
let mouse_down = window.get_mouse_down(MouseButton::Left);
|
||||||
|
|
||||||
|
form.update_and_draw(&mut renderer, mx as i32, my as i32, mouse_down);
|
||||||
|
|
||||||
|
window
|
||||||
|
.update_with_buffer(&renderer.buffer, phys_w, phys_h)
|
||||||
|
.expect("Failed to update window buffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "clean_flash_ui"
|
||||||
|
version = "34.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ab_glyph = { workspace = true }
|
||||||
|
minifb = { workspace = true }
|
||||||
|
windows-sys = { workspace = true }
|
||||||
@ -0,0 +1,179 @@
|
|||||||
|
use ab_glyph::{point, Font, FontRef, ScaleFont};
|
||||||
|
use crate::renderer::Renderer;
|
||||||
|
|
||||||
|
/// Manages loaded fonts and provides text measurement / rendering.
|
||||||
|
pub struct FontManager {
|
||||||
|
regular: FontRef<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontManager {
|
||||||
|
/// Create a `FontManager` using the bundled Liberation Sans font.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let regular = FontRef::try_from_slice(include_bytes!(
|
||||||
|
"../../../resources/liberation-sans.regular.ttf"
|
||||||
|
))
|
||||||
|
.expect("Failed to parse bundled Liberation Sans font");
|
||||||
|
Self { regular }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Measure the width (in pixels) of `text` at the given `size` (in px).
|
||||||
|
pub fn measure_text(&self, text: &str, size: f32) -> (f32, f32) {
|
||||||
|
let scaled = self.regular.as_scaled(size);
|
||||||
|
let mut width: f32 = 0.0;
|
||||||
|
let height = scaled.height();
|
||||||
|
|
||||||
|
let mut last_glyph_id = None;
|
||||||
|
|
||||||
|
for ch in text.chars() {
|
||||||
|
let glyph_id = scaled.glyph_id(ch);
|
||||||
|
if let Some(prev) = last_glyph_id {
|
||||||
|
width += scaled.kern(prev, glyph_id);
|
||||||
|
}
|
||||||
|
width += scaled.h_advance(glyph_id);
|
||||||
|
last_glyph_id = Some(glyph_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw `text` onto the renderer at (x, y) with the given pixel size and colour.
|
||||||
|
pub fn draw_text(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
text: &str,
|
||||||
|
size: f32,
|
||||||
|
color: u32,
|
||||||
|
) {
|
||||||
|
let scaled = self.regular.as_scaled(size);
|
||||||
|
let ascent = scaled.ascent();
|
||||||
|
let mut cursor_x: f32 = 0.0;
|
||||||
|
let mut last_glyph_id = None;
|
||||||
|
|
||||||
|
for ch in text.chars() {
|
||||||
|
let glyph_id = scaled.glyph_id(ch);
|
||||||
|
if let Some(prev) = last_glyph_id {
|
||||||
|
cursor_x += scaled.kern(prev, glyph_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let glyph = glyph_id.with_scale_and_position(
|
||||||
|
size,
|
||||||
|
point(x as f32 + cursor_x, y as f32 + ascent),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(outlined) = self.regular.outline_glyph(glyph) {
|
||||||
|
let bounds = outlined.px_bounds();
|
||||||
|
outlined.draw(|gx, gy, coverage| {
|
||||||
|
let px = bounds.min.x as i32 + gx as i32;
|
||||||
|
let py = bounds.min.y as i32 + gy as i32;
|
||||||
|
let alpha = (coverage * 255.0) as u8;
|
||||||
|
if alpha > 0 {
|
||||||
|
renderer.blend_pixel(px, py, color, alpha);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor_x += scaled.h_advance(glyph_id);
|
||||||
|
last_glyph_id = Some(glyph_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw multiline text, splitting on '\n' and word-wrapping at `max_width`
|
||||||
|
/// (if > 0). Returns total height drawn.
|
||||||
|
pub fn draw_text_multiline(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
text: &str,
|
||||||
|
size: f32,
|
||||||
|
color: u32,
|
||||||
|
line_spacing: f32,
|
||||||
|
max_width: f32,
|
||||||
|
) -> f32 {
|
||||||
|
let scaled = self.regular.as_scaled(size);
|
||||||
|
let line_height = scaled.height() + line_spacing;
|
||||||
|
let mut cy = y as f32;
|
||||||
|
|
||||||
|
for line in text.split('\n') {
|
||||||
|
if max_width > 0.0 {
|
||||||
|
let wrapped = self.word_wrap(line, size, max_width);
|
||||||
|
for wl in &wrapped {
|
||||||
|
self.draw_text(renderer, x, cy as i32, wl, size, color);
|
||||||
|
cy += line_height;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.draw_text(renderer, x, cy as i32, line, size, color);
|
||||||
|
cy += line_height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cy - y as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Measure the total height of multiline text with word-wrapping.
|
||||||
|
pub fn measure_text_multiline(&self, text: &str, size: f32, line_spacing: f32, max_width: f32) -> (f32, f32) {
|
||||||
|
let scaled = self.regular.as_scaled(size);
|
||||||
|
let line_height = scaled.height() + line_spacing;
|
||||||
|
let mut total_lines = 0usize;
|
||||||
|
let mut overall_max_w: f32 = 0.0;
|
||||||
|
|
||||||
|
for line in text.split('\n') {
|
||||||
|
if max_width > 0.0 {
|
||||||
|
let wrapped = self.word_wrap(line, size, max_width);
|
||||||
|
for wl in &wrapped {
|
||||||
|
let (lw, _) = self.measure_text(wl, size);
|
||||||
|
if lw > overall_max_w {
|
||||||
|
overall_max_w = lw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total_lines += wrapped.len();
|
||||||
|
} else {
|
||||||
|
let (lw, _) = self.measure_text(line, size);
|
||||||
|
if lw > overall_max_w {
|
||||||
|
overall_max_w = lw;
|
||||||
|
}
|
||||||
|
total_lines += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(overall_max_w, line_height * total_lines as f32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Word-wrap a single line to fit within `max_width` pixels.
|
||||||
|
fn word_wrap(&self, line: &str, size: f32, max_width: f32) -> Vec<String> {
|
||||||
|
if line.is_empty() {
|
||||||
|
return vec![String::new()];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let mut current = String::new();
|
||||||
|
let mut current_w: f32 = 0.0;
|
||||||
|
let space_w = self.measure_text(" ", size).0;
|
||||||
|
|
||||||
|
for word in line.split_whitespace() {
|
||||||
|
let (ww, _) = self.measure_text(word, size);
|
||||||
|
|
||||||
|
if current.is_empty() {
|
||||||
|
// First word on this line — always add even if too wide.
|
||||||
|
current = word.to_string();
|
||||||
|
current_w = ww;
|
||||||
|
} else if current_w + space_w + ww <= max_width {
|
||||||
|
current.push(' ');
|
||||||
|
current.push_str(word);
|
||||||
|
current_w += space_w + ww;
|
||||||
|
} else {
|
||||||
|
// Wrap.
|
||||||
|
result.push(current);
|
||||||
|
current = word.to_string();
|
||||||
|
current_w = ww;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(current);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
pub mod flash_logo;
|
||||||
|
pub mod font;
|
||||||
|
pub mod renderer;
|
||||||
|
pub mod widgets;
|
||||||
|
|
||||||
|
pub use font::FontManager;
|
||||||
|
pub use renderer::Renderer;
|
||||||
|
|
||||||
|
/// Query the system DPI scale factor (1.0 = 100%, 1.5 = 150%, 2.0 = 200%).
|
||||||
|
/// The process must already be DPI-aware (declared in the manifest) for this
|
||||||
|
/// to return the true hardware DPI rather than the virtualised 96.
|
||||||
|
pub fn get_dpi_scale() -> f32 {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
unsafe {
|
||||||
|
use windows_sys::Win32::UI::HiDpi::GetDpiForSystem;
|
||||||
|
let dpi = GetDpiForSystem();
|
||||||
|
if dpi == 0 { 1.0 } else { dpi as f32 / 96.0 }
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
{ 1.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the window icon from the icon resource already embedded in the binary
|
||||||
|
/// by `winresource` (resource ID 1). No-op on non-Windows platforms.
|
||||||
|
pub fn set_window_icon(window: &minifb::Window) {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
unsafe {
|
||||||
|
use windows_sys::Win32::System::LibraryLoader::GetModuleHandleW;
|
||||||
|
use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||||
|
LoadImageW, SendMessageW, ICON_BIG, ICON_SMALL, IMAGE_ICON, LR_DEFAULTSIZE,
|
||||||
|
WM_SETICON,
|
||||||
|
};
|
||||||
|
|
||||||
|
let hwnd = window.get_window_handle(); // *mut c_void
|
||||||
|
let hmodule = GetModuleHandleW(std::ptr::null());
|
||||||
|
// MAKEINTRESOURCEW(1): load the icon embedded by winresource as resource ID 1.
|
||||||
|
let icon = LoadImageW(hmodule, 1 as *const u16, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
|
||||||
|
if !icon.is_null() {
|
||||||
|
SendMessageW(hwnd, WM_SETICON, ICON_BIG as usize, icon as isize);
|
||||||
|
SendMessageW(hwnd, WM_SETICON, ICON_SMALL as usize, icon as isize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,135 @@
|
|||||||
|
/// Software renderer operating on a `Vec<u32>` pixel buffer (0xAA_RR_GG_BB).
|
||||||
|
/// All drawing is done in-memory; the buffer is presented via minifb.
|
||||||
|
pub struct Renderer {
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
pub buffer: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderer {
|
||||||
|
pub fn new(width: usize, height: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
buffer: vec![0; width * height],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pack r, g, b into the minifb pixel format (0x00RRGGBB).
|
||||||
|
#[inline]
|
||||||
|
pub const fn rgb(r: u8, g: u8, b: u8) -> u32 {
|
||||||
|
((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear the entire buffer to a single colour.
|
||||||
|
pub fn clear(&mut self, color: u32) {
|
||||||
|
self.buffer.fill(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a single pixel (bounds-checked).
|
||||||
|
#[inline]
|
||||||
|
pub fn set_pixel(&mut self, x: i32, y: i32, color: u32) {
|
||||||
|
if x >= 0 && y >= 0 && (x as usize) < self.width && (y as usize) < self.height {
|
||||||
|
self.buffer[y as usize * self.width + x as usize] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Alpha-blend a single pixel. `alpha` is 0..=255.
|
||||||
|
#[inline]
|
||||||
|
pub fn blend_pixel(&mut self, x: i32, y: i32, color: u32, alpha: u8) {
|
||||||
|
if x < 0 || y < 0 || (x as usize) >= self.width || (y as usize) >= self.height {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let idx = y as usize * self.width + x as usize;
|
||||||
|
let dst = self.buffer[idx];
|
||||||
|
self.buffer[idx] = alpha_blend(dst, color, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fill a solid rectangle.
|
||||||
|
pub fn fill_rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: u32) {
|
||||||
|
let x0 = x.max(0) as usize;
|
||||||
|
let y0 = y.max(0) as usize;
|
||||||
|
let x1 = ((x + w) as usize).min(self.width);
|
||||||
|
let y1 = ((y + h) as usize).min(self.height);
|
||||||
|
|
||||||
|
for row in y0..y1 {
|
||||||
|
let start = row * self.width + x0;
|
||||||
|
let end = row * self.width + x1;
|
||||||
|
self.buffer[start..end].fill(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw a 1px rectangle outline.
|
||||||
|
pub fn draw_rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: u32) {
|
||||||
|
// Top / bottom
|
||||||
|
for dx in 0..w {
|
||||||
|
self.set_pixel(x + dx, y, color);
|
||||||
|
self.set_pixel(x + dx, y + h - 1, color);
|
||||||
|
}
|
||||||
|
// Left / right
|
||||||
|
for dy in 0..h {
|
||||||
|
self.set_pixel(x, y + dy, color);
|
||||||
|
self.set_pixel(x + w - 1, y + dy, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fill a rectangle with a vertical linear gradient from `color1` (top) to `color2` (bottom).
|
||||||
|
pub fn fill_gradient_v(&mut self, x: i32, y: i32, w: i32, h: i32, color1: u32, color2: u32) {
|
||||||
|
if h <= 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let (r1, g1, b1) = unpack(color1);
|
||||||
|
let (r2, g2, b2) = unpack(color2);
|
||||||
|
|
||||||
|
for dy in 0..h {
|
||||||
|
let t = dy as f32 / (h - 1).max(1) as f32;
|
||||||
|
let r = lerp_u8(r1, r2, t);
|
||||||
|
let g = lerp_u8(g1, g2, t);
|
||||||
|
let b = lerp_u8(b1, b2, t);
|
||||||
|
let c = Self::rgb(r, g, b);
|
||||||
|
self.fill_rect(x, y + dy, w, 1, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fill a rectangle with a horizontal linear gradient.
|
||||||
|
pub fn fill_gradient_h(&mut self, x: i32, y: i32, w: i32, h: i32, color1: u32, color2: u32) {
|
||||||
|
if w <= 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let (r1, g1, b1) = unpack(color1);
|
||||||
|
let (r2, g2, b2) = unpack(color2);
|
||||||
|
|
||||||
|
for dx in 0..w {
|
||||||
|
let t = dx as f32 / (w - 1).max(1) as f32;
|
||||||
|
let r = lerp_u8(r1, r2, t);
|
||||||
|
let g = lerp_u8(g1, g2, t);
|
||||||
|
let b = lerp_u8(b1, b2, t);
|
||||||
|
let c = Self::rgb(r, g, b);
|
||||||
|
self.fill_rect(x + dx, y, 1, h, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- helpers ----
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn unpack(c: u32) -> (u8, u8, u8) {
|
||||||
|
(((c >> 16) & 0xFF) as u8, ((c >> 8) & 0xFF) as u8, (c & 0xFF) as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn lerp_u8(a: u8, b: u8, t: f32) -> u8 {
|
||||||
|
(a as f32 + (b as f32 - a as f32) * t).round() as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn alpha_blend(dst: u32, src: u32, alpha: u8) -> u32 {
|
||||||
|
let (sr, sg, sb) = unpack(src);
|
||||||
|
let (dr, dg, db) = unpack(dst);
|
||||||
|
let a = alpha as u16;
|
||||||
|
let inv = 255 - a;
|
||||||
|
let r = ((sr as u16 * a + dr as u16 * inv) / 255) as u8;
|
||||||
|
let g = ((sg as u16 * a + dg as u16 * inv) / 255) as u8;
|
||||||
|
let b = ((sb as u16 * a + db as u16 * inv) / 255) as u8;
|
||||||
|
Renderer::rgb(r, g, b)
|
||||||
|
}
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
use super::Rect;
|
||||||
|
use crate::font::FontManager;
|
||||||
|
use crate::renderer::Renderer;
|
||||||
|
|
||||||
|
/// A gradient button matching the C# GradientButton control.
|
||||||
|
pub struct GradientButton {
|
||||||
|
pub rect: Rect,
|
||||||
|
pub text: String,
|
||||||
|
pub font_size: f32,
|
||||||
|
pub color1: u32,
|
||||||
|
pub color2: u32,
|
||||||
|
pub back_color: u32,
|
||||||
|
pub fore_color: u32,
|
||||||
|
pub hover_alpha: f64,
|
||||||
|
pub disable_alpha: f64,
|
||||||
|
pub enabled: bool,
|
||||||
|
pub visible: bool,
|
||||||
|
pub hovered: bool,
|
||||||
|
pub pressed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GradientButton {
|
||||||
|
pub fn new(x: i32, y: i32, w: i32, h: i32, text: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
rect: Rect::new(x, y, w, h),
|
||||||
|
text: text.to_string(),
|
||||||
|
font_size: 13.0,
|
||||||
|
color1: Renderer::rgb(118, 118, 118),
|
||||||
|
color2: Renderer::rgb(81, 81, 81),
|
||||||
|
back_color: Renderer::rgb(0, 0, 0),
|
||||||
|
fore_color: Renderer::rgb(227, 227, 227),
|
||||||
|
hover_alpha: 0.875,
|
||||||
|
disable_alpha: 0.644,
|
||||||
|
enabled: true,
|
||||||
|
visible: true,
|
||||||
|
hovered: false,
|
||||||
|
pressed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update hover / pressed state from mouse position and button state.
|
||||||
|
pub fn update(&mut self, mx: i32, my: i32, mouse_down: bool) {
|
||||||
|
if !self.visible || !self.enabled {
|
||||||
|
self.hovered = false;
|
||||||
|
self.pressed = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.hovered = self.rect.contains(mx, my);
|
||||||
|
self.pressed = self.hovered && mouse_down;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if mouse was just released inside this button.
|
||||||
|
pub fn clicked(&self, mx: i32, my: i32, mouse_released: bool) -> bool {
|
||||||
|
self.visible && self.enabled && self.rect.contains(mx, my) && mouse_released
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&self, renderer: &mut Renderer, fonts: &FontManager) {
|
||||||
|
if !self.visible {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (mut c1, mut c2, mut bg, mut fg) = (self.color1, self.color2, self.back_color, self.fore_color);
|
||||||
|
|
||||||
|
if !self.enabled {
|
||||||
|
c1 = dim_color(c1, self.disable_alpha);
|
||||||
|
c2 = dim_color(c2, self.disable_alpha);
|
||||||
|
bg = dim_color(bg, self.disable_alpha);
|
||||||
|
fg = dim_color(fg, self.disable_alpha);
|
||||||
|
} else if self.pressed {
|
||||||
|
c1 = dim_color(c1, self.hover_alpha);
|
||||||
|
c2 = dim_color(c2, self.hover_alpha);
|
||||||
|
} else if !self.hovered {
|
||||||
|
c1 = dim_color(c1, self.hover_alpha);
|
||||||
|
c2 = dim_color(c2, self.hover_alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = self.rect;
|
||||||
|
renderer.fill_gradient_v(r.x, r.y, r.w, r.h, c1, c2);
|
||||||
|
renderer.draw_rect(r.x, r.y, r.w, r.h, bg);
|
||||||
|
|
||||||
|
// Measure text to centre it.
|
||||||
|
let font_size = self.font_size;
|
||||||
|
let (tw, th) = fonts.measure_text(&self.text, font_size);
|
||||||
|
let tx = r.x + ((r.w as f32 - tw) / 2.0) as i32;
|
||||||
|
let ty = r.y + ((r.h as f32 - th) / 2.0) as i32;
|
||||||
|
|
||||||
|
// Shadow.
|
||||||
|
fonts.draw_text(renderer, tx + 1, ty + 1, &self.text, font_size, bg);
|
||||||
|
// Foreground.
|
||||||
|
fonts.draw_text(renderer, tx, ty, &self.text, font_size, fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dim_color(c: u32, alpha: f64) -> u32 {
|
||||||
|
let r = (((c >> 16) & 0xFF) as f64 * alpha) as u8;
|
||||||
|
let g = (((c >> 8) & 0xFF) as f64 * alpha) as u8;
|
||||||
|
let b = ((c & 0xFF) as f64 * alpha) as u8;
|
||||||
|
Renderer::rgb(r, g, b)
|
||||||
|
}
|
||||||
@ -0,0 +1,138 @@
|
|||||||
|
use super::Rect;
|
||||||
|
use crate::renderer::Renderer;
|
||||||
|
|
||||||
|
/// A software-rendered checkbox with a gray gradient, black outline, and white checkmark.
|
||||||
|
pub struct ImageCheckBox {
|
||||||
|
pub rect: Rect,
|
||||||
|
pub checked: bool,
|
||||||
|
pub enabled: bool,
|
||||||
|
pub visible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageCheckBox {
|
||||||
|
pub fn new(x: i32, y: i32) -> Self {
|
||||||
|
Self::with_size(x, y, 21)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_size(x: i32, y: i32, size: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
rect: Rect::new(x, y, size, size),
|
||||||
|
checked: true,
|
||||||
|
enabled: true,
|
||||||
|
visible: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_if_clicked(&mut self, mx: i32, my: i32, mouse_released: bool) -> bool {
|
||||||
|
if self.visible && self.enabled && self.rect.contains(mx, my) && mouse_released {
|
||||||
|
self.checked = !self.checked;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&self, renderer: &mut Renderer) {
|
||||||
|
if !self.visible {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = self.rect;
|
||||||
|
|
||||||
|
// Gray gradient background (matching button style).
|
||||||
|
let color1 = Renderer::rgb(118, 118, 118);
|
||||||
|
let color2 = Renderer::rgb(81, 81, 81);
|
||||||
|
renderer.fill_gradient_v(r.x, r.y, r.w, r.h, color1, color2);
|
||||||
|
|
||||||
|
// Black 1px outline.
|
||||||
|
renderer.draw_rect(r.x, r.y, r.w, r.h, Renderer::rgb(0, 0, 0));
|
||||||
|
|
||||||
|
// White checkmark when checked.
|
||||||
|
if self.checked {
|
||||||
|
let white = Renderer::rgb(255, 255, 255);
|
||||||
|
Self::draw_checkmark(renderer, r.x, r.y, r.w, r.h, white);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.enabled {
|
||||||
|
// Dim overlay.
|
||||||
|
for dy in 0..r.h {
|
||||||
|
for dx in 0..r.w {
|
||||||
|
renderer.blend_pixel(
|
||||||
|
r.x + dx,
|
||||||
|
r.y + dy,
|
||||||
|
Renderer::rgb(50, 51, 51),
|
||||||
|
100,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw a checkmark scaled to fit within the given box.
|
||||||
|
fn draw_checkmark(renderer: &mut Renderer, bx: i32, by: i32, bw: i32, bh: i32, color: u32) {
|
||||||
|
// Checkmark geometry defined in a normalised coordinate space.
|
||||||
|
// The check goes from bottom-left, down to a valley, then up to top-right.
|
||||||
|
// Key points (in fractions of size):
|
||||||
|
// start: (0.20, 0.50)
|
||||||
|
// valley: (0.40, 0.72)
|
||||||
|
// end: (0.80, 0.25)
|
||||||
|
// We draw two thick line segments between these points.
|
||||||
|
|
||||||
|
let w = bw as f32;
|
||||||
|
let h = bh as f32;
|
||||||
|
|
||||||
|
// Absolute coordinates of the three key points.
|
||||||
|
let x0 = bx as f32 + w * 0.20;
|
||||||
|
let y0 = by as f32 + h * 0.48;
|
||||||
|
let x1 = bx as f32 + w * 0.40;
|
||||||
|
let y1 = by as f32 + h * 0.72;
|
||||||
|
let x2 = bx as f32 + w * 0.80;
|
||||||
|
let y2 = by as f32 + h * 0.25;
|
||||||
|
|
||||||
|
// Line thickness scales with checkbox size.
|
||||||
|
let thickness = (w * 0.14).max(1.5);
|
||||||
|
|
||||||
|
draw_thick_line(renderer, x0, y0, x1, y1, thickness, color);
|
||||||
|
draw_thick_line(renderer, x1, y1, x2, y2, thickness, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw a thick line between two points using filled circles along the path (round caps).
|
||||||
|
fn draw_thick_line(
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
x0: f32,
|
||||||
|
y0: f32,
|
||||||
|
x1: f32,
|
||||||
|
y1: f32,
|
||||||
|
thickness: f32,
|
||||||
|
color: u32,
|
||||||
|
) {
|
||||||
|
let dx = x1 - x0;
|
||||||
|
let dy = y1 - y0;
|
||||||
|
let dist = (dx * dx + dy * dy).sqrt();
|
||||||
|
let steps = (dist * 2.0).ceil() as i32;
|
||||||
|
let half = thickness / 2.0;
|
||||||
|
|
||||||
|
for i in 0..=steps {
|
||||||
|
let t = i as f32 / steps.max(1) as f32;
|
||||||
|
let cx = x0 + dx * t;
|
||||||
|
let cy = y0 + dy * t;
|
||||||
|
|
||||||
|
// Fill a small circle at (cx, cy).
|
||||||
|
let min_x = (cx - half).floor() as i32;
|
||||||
|
let max_x = (cx + half).ceil() as i32;
|
||||||
|
let min_y = (cy - half).floor() as i32;
|
||||||
|
let max_y = (cy + half).ceil() as i32;
|
||||||
|
let r_sq = half * half;
|
||||||
|
|
||||||
|
for py in min_y..=max_y {
|
||||||
|
for px in min_x..=max_x {
|
||||||
|
let fdx = px as f32 + 0.5 - cx;
|
||||||
|
let fdy = py as f32 + 0.5 - cy;
|
||||||
|
if fdx * fdx + fdy * fdy <= r_sq {
|
||||||
|
renderer.set_pixel(px, py, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
use super::Rect;
|
||||||
|
use crate::font::FontManager;
|
||||||
|
use crate::renderer::Renderer;
|
||||||
|
|
||||||
|
/// Simple static label for drawing text with optional word-wrapping.
|
||||||
|
pub struct Label {
|
||||||
|
pub rect: Rect,
|
||||||
|
pub text: String,
|
||||||
|
pub color: u32,
|
||||||
|
pub font_size: f32,
|
||||||
|
pub line_spacing: f32,
|
||||||
|
pub visible: bool,
|
||||||
|
/// If > 0, text is word-wrapped to this pixel width.
|
||||||
|
pub max_width: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Label {
|
||||||
|
pub fn new(x: i32, y: i32, text: &str, font_size: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
rect: Rect::new(x, y, 0, 0),
|
||||||
|
text: text.to_string(),
|
||||||
|
color: Renderer::rgb(245, 245, 245),
|
||||||
|
font_size,
|
||||||
|
line_spacing: 2.0,
|
||||||
|
visible: true,
|
||||||
|
max_width: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&self, renderer: &mut Renderer, fonts: &FontManager) {
|
||||||
|
if !self.visible || self.text.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fonts.draw_text_multiline(
|
||||||
|
renderer,
|
||||||
|
self.rect.x,
|
||||||
|
self.rect.y,
|
||||||
|
&self.text,
|
||||||
|
self.font_size,
|
||||||
|
self.color,
|
||||||
|
self.line_spacing,
|
||||||
|
self.max_width,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a click at (mx, my) is within a rough bounding box of the label text.
|
||||||
|
pub fn clicked(&self, mx: i32, my: i32, mouse_released: bool, fonts: &FontManager) -> bool {
|
||||||
|
if !self.visible || !mouse_released {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let (tw, th) = fonts.measure_text_multiline(
|
||||||
|
&self.text,
|
||||||
|
self.font_size,
|
||||||
|
self.line_spacing,
|
||||||
|
self.max_width,
|
||||||
|
);
|
||||||
|
let r = Rect::new(self.rect.x, self.rect.y, tw as i32 + 5, th as i32);
|
||||||
|
r.contains(mx, my)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
pub mod button;
|
||||||
|
pub mod checkbox;
|
||||||
|
pub mod label;
|
||||||
|
pub mod progress_bar;
|
||||||
|
|
||||||
|
/// A rectangle region on screen.
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Rect {
|
||||||
|
pub x: i32,
|
||||||
|
pub y: i32,
|
||||||
|
pub w: i32,
|
||||||
|
pub h: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rect {
|
||||||
|
pub const fn new(x: i32, y: i32, w: i32, h: i32) -> Self {
|
||||||
|
Self { x, y, w, h }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, px: i32, py: i32) -> bool {
|
||||||
|
px >= self.x && px < self.x + self.w && py >= self.y && py < self.y + self.h
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
use super::Rect;
|
||||||
|
use crate::renderer::Renderer;
|
||||||
|
|
||||||
|
/// A smooth gradient progress bar matching the C# SmoothProgressBar control.
|
||||||
|
pub struct ProgressBar {
|
||||||
|
pub rect: Rect,
|
||||||
|
pub minimum: i32,
|
||||||
|
pub maximum: i32,
|
||||||
|
pub value: i32,
|
||||||
|
pub color1: u32,
|
||||||
|
pub color2: u32,
|
||||||
|
pub visible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgressBar {
|
||||||
|
pub fn new(x: i32, y: i32, w: i32, h: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
rect: Rect::new(x, y, w, h),
|
||||||
|
minimum: 0,
|
||||||
|
maximum: 100,
|
||||||
|
value: 0,
|
||||||
|
color1: Renderer::rgb(97, 147, 232),
|
||||||
|
color2: Renderer::rgb(28, 99, 232),
|
||||||
|
visible: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&self, renderer: &mut Renderer) {
|
||||||
|
if !self.visible {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let range = (self.maximum - self.minimum).max(1) as f32;
|
||||||
|
let percent = (self.value - self.minimum) as f32 / range;
|
||||||
|
let fill_w = (self.rect.w as f32 * percent) as i32;
|
||||||
|
|
||||||
|
if fill_w > 0 {
|
||||||
|
renderer.fill_gradient_h(
|
||||||
|
self.rect.x,
|
||||||
|
self.rect.y,
|
||||||
|
fill_w,
|
||||||
|
self.rect.h,
|
||||||
|
self.color1,
|
||||||
|
self.color2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3-D border.
|
||||||
|
let r = self.rect;
|
||||||
|
let dark = Renderer::rgb(105, 105, 105);
|
||||||
|
let light = Renderer::rgb(255, 255, 255);
|
||||||
|
// Top
|
||||||
|
for dx in 0..r.w {
|
||||||
|
renderer.set_pixel(r.x + dx, r.y, dark);
|
||||||
|
}
|
||||||
|
// Left
|
||||||
|
for dy in 0..r.h {
|
||||||
|
renderer.set_pixel(r.x, r.y + dy, dark);
|
||||||
|
}
|
||||||
|
// Bottom
|
||||||
|
for dx in 0..r.w {
|
||||||
|
renderer.set_pixel(r.x + dx, r.y + r.h - 1, light);
|
||||||
|
}
|
||||||
|
// Right
|
||||||
|
for dy in 0..r.h {
|
||||||
|
renderer.set_pixel(r.x + r.w - 1, r.y + dy, light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "clean_flash_uninstaller"
|
||||||
|
version = "34.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["FlashPatch Team"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clean_flash_common = { path = "../clean_flash_common" }
|
||||||
|
clean_flash_ui = { path = "../clean_flash_ui" }
|
||||||
|
minifb = { workspace = true }
|
||||||
|
windows-sys = { workspace = true }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
winresource = "0.1"
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
fn main() {
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
let mut res = winresource::WindowsResource::new();
|
||||||
|
if std::path::Path::new("../../resources/icon.ico").exists() {
|
||||||
|
res.set_icon("../../resources/icon.ico");
|
||||||
|
}
|
||||||
|
res.set("ProductName", "Clean Flash Player Uninstaller");
|
||||||
|
res.set("FileDescription", "Clean Flash Player Uninstaller");
|
||||||
|
res.set("ProductVersion", "34.0.0.330");
|
||||||
|
res.set("CompanyName", "FlashPatch Team");
|
||||||
|
res.set("LegalCopyright", "FlashPatch Team");
|
||||||
|
res.set_manifest(r#"<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="CleanFlashUninstaller.app"/>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||||
|
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||||
|
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||||
|
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||||
|
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
</assembly>
|
||||||
|
"#);
|
||||||
|
let _ = res.compile();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
#![windows_subsystem = "windows"]
|
||||||
|
|
||||||
|
mod uninstall_form;
|
||||||
|
|
||||||
|
use clean_flash_ui::renderer::Renderer;
|
||||||
|
use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions};
|
||||||
|
use uninstall_form::{UninstallForm, HEIGHT, WIDTH};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let title = format!(
|
||||||
|
"Clean Flash Player {} Uninstaller",
|
||||||
|
clean_flash_common::update_checker::FLASH_VERSION
|
||||||
|
);
|
||||||
|
|
||||||
|
let scale = clean_flash_ui::get_dpi_scale();
|
||||||
|
let phys_w = (WIDTH as f32 * scale).round() as usize;
|
||||||
|
let phys_h = (HEIGHT as f32 * scale).round() as usize;
|
||||||
|
|
||||||
|
let mut window = Window::new(
|
||||||
|
&title,
|
||||||
|
phys_w,
|
||||||
|
phys_h,
|
||||||
|
WindowOptions {
|
||||||
|
resize: false,
|
||||||
|
..WindowOptions::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("Failed to create window");
|
||||||
|
|
||||||
|
// Set window icon from the resource embedded by build.rs.
|
||||||
|
clean_flash_ui::set_window_icon(&window);
|
||||||
|
|
||||||
|
window.set_target_fps(60);
|
||||||
|
|
||||||
|
let mut renderer = Renderer::new(phys_w, phys_h);
|
||||||
|
let mut form = UninstallForm::new(scale);
|
||||||
|
|
||||||
|
while window.is_open() && !window.is_key_down(Key::Escape)
|
||||||
|
&& !(window.is_key_down(Key::F4)
|
||||||
|
&& (window.is_key_down(Key::LeftAlt) || window.is_key_down(Key::RightAlt)))
|
||||||
|
{
|
||||||
|
let (mx, my) = window
|
||||||
|
.get_mouse_pos(MouseMode::Clamp)
|
||||||
|
.unwrap_or((0.0, 0.0));
|
||||||
|
let mouse_down = window.get_mouse_down(MouseButton::Left);
|
||||||
|
|
||||||
|
form.update_and_draw(&mut renderer, mx as i32, my as i32, mouse_down);
|
||||||
|
|
||||||
|
window
|
||||||
|
.update_with_buffer(&renderer.buffer, phys_w, phys_h)
|
||||||
|
.expect("Failed to update window buffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,292 @@
|
|||||||
|
use clean_flash_common::{redirection, uninstaller, update_checker, ProgressCallback};
|
||||||
|
use clean_flash_ui::font::FontManager;
|
||||||
|
use clean_flash_ui::renderer::Renderer;
|
||||||
|
use clean_flash_ui::widgets::button::GradientButton;
|
||||||
|
use clean_flash_ui::widgets::label::Label;
|
||||||
|
use clean_flash_ui::widgets::progress_bar::ProgressBar;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
pub const WIDTH: usize = 712;
|
||||||
|
pub const HEIGHT: usize = 329;
|
||||||
|
const BG_COLOR: u32 = 0x00323333; // RGB(50, 51, 51)
|
||||||
|
const FG_COLOR: u32 = 0x00F5F5F5;
|
||||||
|
const PANEL_X: i32 = 90;
|
||||||
|
const PANEL_Y: i32 = 162;
|
||||||
|
|
||||||
|
const BEFORE_TEXT: &str = "You are about to uninstall Clean Flash Player.\n\
|
||||||
|
Please close all browsers, including Google Chrome, Mozilla Firefox and Internet Explorer.\n\n\
|
||||||
|
The installer will completely remove all versions of Flash Player from this computer,\n\
|
||||||
|
including Clean Flash Player and older versions of Adobe Flash Player.";
|
||||||
|
|
||||||
|
const COMPLETE_TEXT: &str = "\nAll versions of Flash Player have been successfully uninstalled.\n\n\
|
||||||
|
If you ever change your mind, check out Clean Flash Player's website!";
|
||||||
|
|
||||||
|
const UNINSTALL_TICKS: i32 = 10;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum Panel {
|
||||||
|
BeforeInstall,
|
||||||
|
Install,
|
||||||
|
Complete,
|
||||||
|
Failure,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ProgressState {
|
||||||
|
label: String,
|
||||||
|
value: i32,
|
||||||
|
done: bool,
|
||||||
|
error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UninstallForm {
|
||||||
|
scale: f32,
|
||||||
|
panel: Panel,
|
||||||
|
title_text: String,
|
||||||
|
subtitle_text: String,
|
||||||
|
flash_logo_cache: clean_flash_ui::flash_logo::FlashLogoCache,
|
||||||
|
prev_button: GradientButton,
|
||||||
|
next_button: GradientButton,
|
||||||
|
// Before uninstall
|
||||||
|
before_label: Label,
|
||||||
|
// Install (progress)
|
||||||
|
progress_header: Label,
|
||||||
|
progress_label: Label,
|
||||||
|
progress_bar: ProgressBar,
|
||||||
|
// Complete
|
||||||
|
complete_label: Label,
|
||||||
|
// Failure
|
||||||
|
failure_text_label: Label,
|
||||||
|
failure_detail: String,
|
||||||
|
copy_error_button: GradientButton,
|
||||||
|
// State
|
||||||
|
progress_state: Arc<Mutex<ProgressState>>,
|
||||||
|
fonts: FontManager,
|
||||||
|
prev_mouse_down: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UninstallForm {
|
||||||
|
pub fn new(scale: f32) -> Self {
|
||||||
|
let s = |v: i32| (v as f32 * scale).round() as i32;
|
||||||
|
let sf = |v: f32| v * scale;
|
||||||
|
let lbl = |x: i32, y: i32, text: &str, size: f32| {
|
||||||
|
let mut l = Label::new(s(x), s(y), text, sf(size));
|
||||||
|
l.line_spacing = sf(2.0);
|
||||||
|
l.max_width = sf((WIDTH as i32 - x - 10) as f32);
|
||||||
|
l
|
||||||
|
};
|
||||||
|
let btn = |x: i32, y: i32, w: i32, h: i32, text: &str| GradientButton {
|
||||||
|
font_size: sf(13.0),
|
||||||
|
..GradientButton::new(s(x), s(y), s(w), s(h), text)
|
||||||
|
};
|
||||||
|
|
||||||
|
let version = update_checker::FLASH_VERSION;
|
||||||
|
|
||||||
|
let fonts = FontManager::new();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
scale,
|
||||||
|
panel: Panel::BeforeInstall,
|
||||||
|
title_text: "Clean Flash Player".into(),
|
||||||
|
subtitle_text: format!("built from version {} (China)", version),
|
||||||
|
flash_logo_cache: clean_flash_ui::flash_logo::FlashLogoCache::new(),
|
||||||
|
prev_button: btn(90, 286, 138, 31, "QUIT"),
|
||||||
|
next_button: btn(497, 286, 138, 31, "UNINSTALL"),
|
||||||
|
before_label: lbl(PANEL_X + 3, PANEL_Y + 2, BEFORE_TEXT, 15.0),
|
||||||
|
progress_header: lbl(PANEL_X + 3, PANEL_Y, "Uninstallation in progress...", 15.0),
|
||||||
|
progress_label: lbl(PANEL_X + 46, PANEL_Y + 30, "Preparing...", 15.0),
|
||||||
|
progress_bar: ProgressBar::new(s(PANEL_X + 49), s(PANEL_Y + 58), s(451), s(23)),
|
||||||
|
complete_label: lbl(PANEL_X, PANEL_Y, COMPLETE_TEXT, 15.0),
|
||||||
|
failure_text_label: lbl(
|
||||||
|
PANEL_X + 3,
|
||||||
|
PANEL_Y + 2,
|
||||||
|
"Oops! The installation process has encountered an unexpected problem.\n\
|
||||||
|
The following details could be useful. Press the Retry button to try again.",
|
||||||
|
15.0,
|
||||||
|
),
|
||||||
|
failure_detail: String::new(),
|
||||||
|
copy_error_button: btn(PANEL_X + 441, PANEL_Y + 58, 104, 31, "COPY"),
|
||||||
|
progress_state: Arc::new(Mutex::new(ProgressState {
|
||||||
|
label: "Preparing...".into(),
|
||||||
|
value: 0,
|
||||||
|
done: false,
|
||||||
|
error: None,
|
||||||
|
})),
|
||||||
|
fonts,
|
||||||
|
prev_mouse_down: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_and_draw(
|
||||||
|
&mut self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
mx: i32,
|
||||||
|
my: i32,
|
||||||
|
mouse_down: bool,
|
||||||
|
) {
|
||||||
|
let mouse_released = self.prev_mouse_down && !mouse_down;
|
||||||
|
self.prev_mouse_down = mouse_down;
|
||||||
|
|
||||||
|
self.prev_button.update(mx, my, mouse_down);
|
||||||
|
self.next_button.update(mx, my, mouse_down);
|
||||||
|
|
||||||
|
// Handle clicks.
|
||||||
|
if self.prev_button.clicked(mx, my, mouse_released) {
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
if self.next_button.clicked(mx, my, mouse_released) {
|
||||||
|
match self.panel {
|
||||||
|
Panel::BeforeInstall | Panel::Failure => self.start_uninstall(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panel-specific input.
|
||||||
|
if self.panel == Panel::Failure {
|
||||||
|
self.copy_error_button.update(mx, my, mouse_down);
|
||||||
|
if self.copy_error_button.clicked(mx, my, mouse_released) {
|
||||||
|
let _ = std::process::Command::new("cmd")
|
||||||
|
.args(["/C", &format!("echo {} | clip", self.failure_detail)])
|
||||||
|
.output();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll progress.
|
||||||
|
if self.panel == Panel::Install {
|
||||||
|
self.poll_progress();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw.
|
||||||
|
renderer.clear(BG_COLOR);
|
||||||
|
self.flash_logo_cache.draw(
|
||||||
|
renderer, self.s(90), self.s(36), self.s(109), self.s(107),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.fonts
|
||||||
|
.draw_text(renderer, self.s(233), self.s(54), &self.title_text, self.sf(32.0), FG_COLOR);
|
||||||
|
self.fonts
|
||||||
|
.draw_text(renderer, self.s(280), self.s(99), &self.subtitle_text, self.sf(17.0), FG_COLOR);
|
||||||
|
|
||||||
|
// Separator.
|
||||||
|
renderer.fill_rect(0, self.s(270), renderer.width as i32, self.s(1).max(1), 0x00696969);
|
||||||
|
|
||||||
|
match self.panel {
|
||||||
|
Panel::BeforeInstall => self.before_label.draw(renderer, &self.fonts),
|
||||||
|
Panel::Install => {
|
||||||
|
self.progress_header.draw(renderer, &self.fonts);
|
||||||
|
self.progress_label.draw(renderer, &self.fonts);
|
||||||
|
self.progress_bar.draw(renderer);
|
||||||
|
}
|
||||||
|
Panel::Complete => self.complete_label.draw(renderer, &self.fonts),
|
||||||
|
Panel::Failure => {
|
||||||
|
self.failure_text_label.draw(renderer, &self.fonts);
|
||||||
|
let detail = if self.failure_detail.len() > 300 {
|
||||||
|
&self.failure_detail[..300]
|
||||||
|
} else {
|
||||||
|
&self.failure_detail
|
||||||
|
};
|
||||||
|
self.fonts.draw_text_multiline(
|
||||||
|
renderer,
|
||||||
|
self.s(PANEL_X + 4),
|
||||||
|
self.s(PANEL_Y + 44),
|
||||||
|
detail,
|
||||||
|
self.sf(11.0),
|
||||||
|
FG_COLOR,
|
||||||
|
self.sf(1.0),
|
||||||
|
self.sf((WIDTH as i32 - PANEL_X - 14) as f32),
|
||||||
|
);
|
||||||
|
self.copy_error_button.draw(renderer, &self.fonts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.prev_button.draw(renderer, &self.fonts);
|
||||||
|
self.next_button.draw(renderer, &self.fonts);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn s(&self, v: i32) -> i32 { (v as f32 * self.scale).round() as i32 }
|
||||||
|
fn sf(&self, v: f32) -> f32 { v * self.scale }
|
||||||
|
|
||||||
|
fn start_uninstall(&mut self) {
|
||||||
|
self.panel = Panel::Install;
|
||||||
|
self.prev_button.enabled = false;
|
||||||
|
self.next_button.visible = false;
|
||||||
|
self.progress_bar.maximum = UNINSTALL_TICKS;
|
||||||
|
self.progress_bar.value = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut state = self.progress_state.lock().unwrap();
|
||||||
|
state.label = "Preparing...".into();
|
||||||
|
state.value = 0;
|
||||||
|
state.done = false;
|
||||||
|
state.error = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let progress = Arc::clone(&self.progress_state);
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let callback = ThreadProgressCallback {
|
||||||
|
state: Arc::clone(&progress),
|
||||||
|
};
|
||||||
|
|
||||||
|
let redir = redirection::disable_redirection();
|
||||||
|
let result = uninstaller::uninstall(&callback);
|
||||||
|
redirection::enable_redirection(redir);
|
||||||
|
|
||||||
|
let mut state = progress.lock().unwrap();
|
||||||
|
if let Err(e) = result {
|
||||||
|
state.error = Some(e.to_string());
|
||||||
|
}
|
||||||
|
state.done = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_progress(&mut self) {
|
||||||
|
let state = self.progress_state.lock().unwrap();
|
||||||
|
self.progress_label.text = state.label.clone();
|
||||||
|
self.progress_bar.value = state.value;
|
||||||
|
|
||||||
|
if state.done {
|
||||||
|
if let Some(ref err) = state.error {
|
||||||
|
self.failure_detail = err.clone();
|
||||||
|
drop(state);
|
||||||
|
self.open_failure();
|
||||||
|
} else {
|
||||||
|
drop(state);
|
||||||
|
self.open_complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_complete(&mut self) {
|
||||||
|
self.panel = Panel::Complete;
|
||||||
|
self.prev_button.text = "QUIT".into();
|
||||||
|
self.prev_button.enabled = true;
|
||||||
|
self.next_button.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_failure(&mut self) {
|
||||||
|
self.panel = Panel::Failure;
|
||||||
|
self.prev_button.text = "QUIT".into();
|
||||||
|
self.prev_button.enabled = true;
|
||||||
|
self.next_button.text = "RETRY".into();
|
||||||
|
self.next_button.visible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThreadProgressCallback {
|
||||||
|
state: Arc<Mutex<ProgressState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgressCallback for ThreadProgressCallback {
|
||||||
|
fn update_progress_label(&self, text: &str, tick: bool) {
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
state.label = text.to_string();
|
||||||
|
if tick {
|
||||||
|
state.value += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick_progress(&self) {
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
state.value += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
Binary file not shown.
Loading…
Reference in New Issue