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