Add Rust port

This commit is contained in:
darktohka
2026-03-17 03:10:07 +02:00
parent 64de0ade31
commit f8a1dde721
38 changed files with 4806 additions and 1 deletions
@@ -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,37 @@
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>
</assembly>
"#);
let _ = res.compile();
}
}
@@ -0,0 +1,72 @@
/// 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;
const UNINSTALL_TICKS: u32 = 9;
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;
}
ticks += UNINSTALL_TICKS;
ticks += INSTALL_GENERAL_TICKS;
ticks
}
}
@@ -0,0 +1,691 @@
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, RgbaImage};
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\n\
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,\n\
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 {
pub panel: Panel,
// Header
pub title_text: String,
pub subtitle_text: String,
pub flash_logo: RgbaImage,
// Checkbox images
pub checkbox_on: RgbaImage,
pub checkbox_off: RgbaImage,
// 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 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() -> Self {
let version = update_checker::FLASH_VERSION;
let title_text = "Clean Flash Player".to_string();
let subtitle_text = format!("built from version {} (China)", version);
// Load images from the common resources folder.
// These are loaded from the C# project's assets alongside the binary.
let flash_logo = load_resource_image("flashLogo.png");
let checkbox_on = load_resource_image("checkboxOn.png");
let checkbox_off = load_resource_image("checkboxOff.png");
let fonts = FontManager::new();
Self {
panel: Panel::Disclaimer,
title_text,
subtitle_text,
flash_logo,
checkbox_on,
checkbox_off,
prev_button: GradientButton::new(90, 286, 138, 31, "QUIT"),
next_button: GradientButton::new(497, 286, 138, 31, "AGREE"),
// Disclaimer panel
disclaimer_label: Label::new(PANEL_X + 25, PANEL_Y, DISCLAIMER_TEXT, 13.0),
disclaimer_box: ImageCheckBox::new(PANEL_X, PANEL_Y),
// Choice panel
browser_ask_label: Label::new(
PANEL_X - 2,
PANEL_Y + 2,
"Which browser plugins would you like to install?",
13.0,
),
pepper_box: ImageCheckBox::new(PANEL_X, PANEL_Y + 47),
pepper_label: Label::new(
PANEL_X + 24,
PANEL_Y + 47,
"Pepper API (PPAPI)\n(Chrome/Opera/Brave)",
13.0,
),
netscape_box: ImageCheckBox::new(PANEL_X + 186, PANEL_Y + 47),
netscape_label: Label::new(
PANEL_X + 210,
PANEL_Y + 47,
"Netscape API (NPAPI)\n(Firefox/ESR/Waterfox)",
13.0,
),
activex_box: ImageCheckBox::new(PANEL_X + 365, PANEL_Y + 47),
activex_label: Label::new(
PANEL_X + 389,
PANEL_Y + 47,
"ActiveX (OCX)\n(IE/Embedded/Desktop)",
13.0,
),
// Player choice panel
player_ask_label: Label::new(
PANEL_X - 2,
PANEL_Y + 2,
"Would you like to install the standalone Flash Player?",
13.0,
),
player_box: ImageCheckBox::new(PANEL_X, PANEL_Y + 47),
player_label: Label::new(
PANEL_X + 24,
PANEL_Y + 47,
"Install Standalone\nFlash Player",
13.0,
),
player_desktop_box: ImageCheckBox::new(PANEL_X + 186, PANEL_Y + 47),
player_desktop_label: Label::new(
PANEL_X + 210,
PANEL_Y + 47,
"Create Shortcuts\non Desktop",
13.0,
),
player_start_menu_box: ImageCheckBox::new(PANEL_X + 365, PANEL_Y + 47),
player_start_menu_label: Label::new(
PANEL_X + 389,
PANEL_Y + 47,
"Create Shortcuts\nin Start Menu",
13.0,
),
// Debug choice panel
debug_ask_label: Label::new(
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.",
13.0,
),
debug_button: GradientButton::new(PANEL_X + 186, PANEL_Y + 65, 176, 31, "INSTALL DEBUG VERSION"),
debug_chosen: false,
// Before install panel
before_install_label: Label::new(PANEL_X + 3, PANEL_Y + 2, "", 13.0),
// Install panel
install_header_label: Label::new(PANEL_X + 3, PANEL_Y, "Installation in progress...", 13.0),
progress_label: Label::new(PANEL_X + 46, PANEL_Y + 30, "Preparing...", 13.0),
progress_bar: ProgressBar::new(PANEL_X + 49, PANEL_Y + 58, 451, 23),
// Complete panel
complete_label: Label::new(PANEL_X, PANEL_Y, "", 13.0),
// Failure panel
failure_text_label: Label::new(
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.",
13.0,
),
failure_detail: String::new(),
copy_error_button: GradientButton::new(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,
}
}
/// 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.
renderer.draw_image(90, 36, &self.flash_logo);
// Title.
self.fonts.draw_text(
renderer,
233,
54,
&self.title_text,
32.0, // ~24pt Segoe UI
FG_COLOR,
);
// Subtitle.
self.fonts.draw_text(
renderer,
280,
99,
&self.subtitle_text,
17.0, // ~13pt Segoe UI
FG_COLOR,
);
// Separator line at y=270.
renderer.fill_rect(0, 270, WIDTH as i32, 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.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.pepper_label.clicked(mx, my, mouse_released, &self.fonts) {
self.pepper_box.checked = !self.pepper_box.checked;
}
if self.netscape_label.clicked(mx, my, mouse_released, &self.fonts) {
self.netscape_box.checked = !self.netscape_box.checked;
}
if 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_label.clicked(mx, my, mouse_released, &self.fonts) {
self.player_box.checked = !self.player_box.checked;
}
if 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_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 => self.open_debug_choice(),
_ => {}
}
}
fn on_next_clicked(&mut self) {
match self.panel {
Panel::Disclaimer => self.open_choice(),
Panel::Choice => self.open_player_choice(),
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.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.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\n\
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,\n\
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.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();
state.done = true;
if let Err(e) = result {
state.error = Some(e.to_string());
}
});
}
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.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.checkbox_on, &self.checkbox_off);
self.disclaimer_label.draw(r, &self.fonts);
}
fn draw_choice(&self, r: &mut Renderer) {
self.browser_ask_label.draw(r, &self.fonts);
self.pepper_box
.draw(r, &self.checkbox_on, &self.checkbox_off);
self.pepper_label.draw(r, &self.fonts);
self.netscape_box
.draw(r, &self.checkbox_on, &self.checkbox_off);
self.netscape_label.draw(r, &self.fonts);
self.activex_box
.draw(r, &self.checkbox_on, &self.checkbox_off);
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.checkbox_on, &self.checkbox_off);
self.player_label.draw(r, &self.fonts);
self.player_desktop_box
.draw(r, &self.checkbox_on, &self.checkbox_off);
self.player_desktop_label.draw(r, &self.fonts);
self.player_start_menu_box
.draw(r, &self.checkbox_on, &self.checkbox_off);
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_y = PANEL_Y + 44;
let detail_text = if self.failure_detail.len() > 300 {
&self.failure_detail[..300]
} else {
&self.failure_detail
};
self.fonts.draw_text_multiline(
r,
PANEL_X + 4,
detail_y,
detail_text,
11.0,
FG_COLOR,
1.0,
);
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)
}
}
}
/// Try to load a resource image from the original C# project's asset folder.
fn load_resource_image(name: &str) -> RgbaImage {
let bytes: &[u8] = match name {
"flashLogo.png" => include_bytes!("../../../resources/flashLogo.png"),
"checkboxOn.png" => include_bytes!("../../../resources/checkboxOn.png"),
"checkboxOff.png" => include_bytes!("../../../resources/checkboxOff.png"),
_ => return RgbaImage::empty(),
};
RgbaImage::from_png_bytes(bytes)
}
@@ -0,0 +1,356 @@
use crate::install_flags::{self, InstallFlags};
use clean_flash_common::{
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 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),
},
),
];
let legacy = si.is_legacy_windows();
// Extract archive using sevenz-rust2.
sevenz_rust2::decompress_with_extract_fn(
Cursor::new(archive_bytes),
".",
|entry, reader, _dest| {
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);
// Find the matching entry.
let Some((_key, install_entry)) = 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.
if install_entry.required_flags != install_flags::NONE
&& !flags.is_set(install_entry.required_flags)
{
io::copy(reader, &mut io::sink()).map_err(sevenz_rust2::Error::from)?;
return Ok(true);
}
// Check debug flag match.
if install_entry.required_flags != install_flags::NONE {
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);
}
}
form.update_progress_label(install_entry.install_text, true);
// Ensure target directory exists.
let _ = fs::create_dir_all(&install_entry.target_directory);
// Extract file: use just the file name (strip any path prefix).
let out_name = parts.last().unwrap_or(&filename);
let out_path = install_entry.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())?;
}
}
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,49 @@
#![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
);
let mut window = Window::new(
&title,
WIDTH,
HEIGHT,
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 ~60 fps.
window.set_target_fps(60);
let mut renderer = Renderer::new(WIDTH, HEIGHT);
let mut form = InstallForm::new();
while window.is_open() && !window.is_key_down(Key::Escape) {
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, WIDTH, HEIGHT)
.expect("Failed to update window buffer");
}
}