using System; using System.IO; using System.Reflection; using System.Drawing; using System.Windows.Forms; using System.Diagnostics; using System.IO.Compression; using System.Net; using System.Text.RegularExpressions; using System.Collections.Generic; [assembly: AssemblyTitle("YouTube Downloader Setup")] [assembly: AssemblyDescription("YouTube Video and Audio Downloader Installer")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("YouTube Downloader Project")] [assembly: AssemblyProduct("YouTube Downloader")] [assembly: AssemblyCopyright("Copyright © 2026 YouTube Downloader")] [assembly: AssemblyTrademark("")] [assembly: AssemblyVersion("1.2.0.0")] [assembly: AssemblyFileVersion("1.2.0.0")] [assembly: AssemblyInformationalVersion("1.2.0")] public class InstallLang { public string Code; public string DisplayName; public bool IsRtl; public string AppTitle; public string WizardTitle; public string HeaderSubtitle; public string DestFolder; public string Browse; public string BrowseDesc; public string ShortcutDesktop; public string ShortcutStart; public string LaunchAfter; public string InstallNow; public string Cancel; public string Ok; public string SelectLangTitle; public string SelectLangPrompt; public string Preparing; public string Connecting; public string CreatingFolder; public string ExtractingLocal; public string CopyingLocal; public string Downloading; public string DownloadingChunk; public string ExtractingDeploying; public string CreatingShortcuts; public string ShortcutsDetail; public string Registering; public string RegisterDetail; public string InstallDone; public string InstallDoneDetail; public string Finish; public string ErrorTitle; public string ErrorPrompt; public string UninstallWizardTitle; public string UninstallHeaderTitle; public string UninstallHeaderSub; public string UninstallPrompt; public string UninstallDetail; public string UninstallAction; public string UninstallProgressTitle; public string UninstallProgressDetail; public string UninstallDoneTitle; public string UninstallDoneDetail; public override string ToString() { return DisplayName; } } public static class I18n { private static readonly Dictionary _langs = new Dictionary(StringComparer.OrdinalIgnoreCase); public static readonly List All = new List(); static I18n() { Add(new InstallLang { Code = "zh-TW", DisplayName = "繁體中文 (Traditional Chinese)", IsRtl = false, AppTitle = "YouTube 影音下載器", WizardTitle = "{0} - 安裝精靈", HeaderSubtitle = "超輕量線上安裝器 | 支援 4K 影片與 MP3 音訊下載", DestFolder = "安裝目的地資料夾:", Browse = "瀏覽...", BrowseDesc = "選擇 YouTube 影音下載器 的安裝目錄", ShortcutDesktop = "在桌面建立捷徑", ShortcutStart = "在開始功能表建立捷徑", LaunchAfter = "安裝完成後立即啟動程式", InstallNow = "立即安裝", Cancel = "取消", Ok = "確定", SelectLangTitle = "選擇安裝語言", SelectLangPrompt = "請選擇安裝時使用的語言:", Preparing = "正在準備安裝環境...", Connecting = "正在連接伺服器...", CreatingFolder = "正在建立安裝資料夾...", ExtractingLocal = "正在從本機離線檔案解壓縮組件...", CopyingLocal = "正在複製本機組件...", Downloading = "正在自雲端下載核心組件...", DownloadingChunk = "正在自雲端下載核心組件 ({0}/{1})...", ExtractingDeploying = "正在解壓縮與部署核心檔案...", CreatingShortcuts = "正在建立捷徑與系統關聯...", ShortcutsDetail = "桌面與開始功能表", Registering = "正在向 Windows 註冊應用程式資訊...", RegisterDetail = "支援 Windows 設定直接解除安裝", InstallDone = "安裝完成!", InstallDoneDetail = "所有組件已配置完畢,已就緒可供使用。", Finish = "完成", ErrorTitle = "安裝錯誤", ErrorPrompt = "安裝過程發生錯誤:\n{0}\n\n提示:若無法自雲端下載,可將 payload.zip 置於安裝檔同目錄下進行離線安裝。", UninstallWizardTitle = "{0} - 解除安裝精靈", UninstallHeaderTitle = "解除安裝 {0}", UninstallHeaderSub = "從您的電腦完全移除此應用程式及其桌面捷徑", UninstallPrompt = "您確定要從這部電腦完整解除安裝「{0}」嗎?", UninstallDetail = "此操作將會永久清理所有安裝的程式檔案、桌面與開始功能表捷徑,以及系統設定清單中的記錄。", UninstallAction = "確定解除安裝", UninstallProgressTitle = "正在解除安裝中,請稍候...", UninstallProgressDetail = "正在結束處理程序並清理檔案捷徑與系統註冊表記錄...", UninstallDoneTitle = "解除安裝已順利完成!", UninstallDoneDetail = "「{0}」已完全從您的電腦中移除。感謝您的使用!" }); Add(new InstallLang { Code = "zh-CN", DisplayName = "简体中文 (Simplified Chinese)", IsRtl = false, AppTitle = "YouTube 影音下载器", WizardTitle = "{0} - 安装向导", HeaderSubtitle = "超轻量在线安装器 | 支持 4K 视频与 MP3 音频下载", DestFolder = "安装目标文件夹:", Browse = "浏览...", BrowseDesc = "选择 YouTube 影音下载器 的安装目录", ShortcutDesktop = "在桌面创建快捷方式", ShortcutStart = "在开始菜单创建快捷方式", LaunchAfter = "安装完成后立即启动程序", InstallNow = "立即安装", Cancel = "取消", Ok = "确定", SelectLangTitle = "选择安装语言", SelectLangPrompt = "请选择安装时使用的语言:", Preparing = "正在准备安装环境...", Connecting = "正在连接服务器...", CreatingFolder = "正在创建安装文件夹...", ExtractingLocal = "正在从本地离线文件解压缩组件...", CopyingLocal = "正在复制本地组件...", Downloading = "正在自云端下载核心组件...", DownloadingChunk = "正在自云端下载核心组件 ({0}/{1})...", ExtractingDeploying = "正在解压缩与部署核心文件...", CreatingShortcuts = "正在创建快捷方式与系统关联...", ShortcutsDetail = "桌面与开始菜单", Registering = "正在向 Windows 注册应用程序信息...", RegisterDetail = "支持 Windows 设置直接卸载", InstallDone = "安装完成!", InstallDoneDetail = "所有组件已配置完毕,已就绪可供使用。", Finish = "完成", ErrorTitle = "安装错误", ErrorPrompt = "安装过程发生错误:\n{0}\n\n提示:若无法自云端下载,可将 payload.zip 置于安装包同目录下进行离线安装。", UninstallWizardTitle = "{0} - 卸载向导", UninstallHeaderTitle = "卸载 {0}", UninstallHeaderSub = "从您的电脑完全移除此应用程序及其桌面快捷方式", UninstallPrompt = "您确定要从这台电脑完整卸载「{0}」吗?", UninstallDetail = "此操作将会永久清理所有安装的程序文件、桌面与开始菜单快捷方式,以及系统设置列表中的记录。", UninstallAction = "确定卸载", UninstallProgressTitle = "正在卸载中,请稍候...", UninstallProgressDetail = "正在结束进程并清理文件快捷方式与系统注册表记录...", UninstallDoneTitle = "卸载已顺利完成!", UninstallDoneDetail = "「{0}」已完全从您的电脑中移除。感谢您的使用!" }); Add(new InstallLang { Code = "en", DisplayName = "English", IsRtl = false, AppTitle = "YouTube Video Downloader", WizardTitle = "{0} - Setup", HeaderSubtitle = "Ultra-lightweight Online Installer | Supports 4K Video & MP3 Audio", DestFolder = "Destination Folder:", Browse = "Browse...", BrowseDesc = "Select Installation Folder for YouTube Video Downloader", ShortcutDesktop = "Create desktop shortcut", ShortcutStart = "Create Start Menu shortcut", LaunchAfter = "Launch application after installation", InstallNow = "Install Now", Cancel = "Cancel", Ok = "OK", SelectLangTitle = "Select Setup Language", SelectLangPrompt = "Select the language to use during the installation:", Preparing = "Preparing installation environment...", Connecting = "Connecting to server...", CreatingFolder = "Creating installation folder...", ExtractingLocal = "Extracting components from local package...", CopyingLocal = "Copying local components...", Downloading = "Downloading core components from cloud...", DownloadingChunk = "Downloading core components ({0}/{1})...", ExtractingDeploying = "Extracting and deploying core files...", CreatingShortcuts = "Creating shortcuts and system associations...", ShortcutsDetail = "Desktop and Start Menu", Registering = "Registering application with Windows...", RegisterDetail = "Enables uninstall via Windows Settings", InstallDone = "Installation Complete!", InstallDoneDetail = "All components have been configured and are ready to use.", Finish = "Finish", ErrorTitle = "Installation Error", ErrorPrompt = "An error occurred during installation:\n{0}\n\nTip: You can place payload.zip in the same directory for offline installation.", UninstallWizardTitle = "{0} - Uninstall Wizard", UninstallHeaderTitle = "Uninstall {0}", UninstallHeaderSub = "Completely remove this application and shortcuts from your computer", UninstallPrompt = "Are you sure you want to completely uninstall \"{0}\"?", UninstallDetail = "This will permanently remove all program files, shortcuts, and system entries.", UninstallAction = "Uninstall", UninstallProgressTitle = "Uninstalling, please wait...", UninstallProgressDetail = "Closing processes and cleaning up files, shortcuts, and registry entries...", UninstallDoneTitle = "Uninstallation Complete", UninstallDoneDetail = "\"{0}\" has been completely removed from your computer. Thank you for using it!" }); Add(new InstallLang { Code = "ja", DisplayName = "日本語 (Japanese)", IsRtl = false, AppTitle = "YouTube 動画ダウンローダー", WizardTitle = "{0} - セットアップ", HeaderSubtitle = "超軽量オンラインインストーラー | 4K 動画&MP3 音声保存対応", DestFolder = "インストール先フォルダー:", Browse = "参照...", BrowseDesc = "YouTube 動画ダウンローダーのインストール先を選択", ShortcutDesktop = "デスクトップ ショートカットを作成する", ShortcutStart = "スタートメニューに登録する", LaunchAfter = "インストール完了後に起動する", InstallNow = "今すぐインストール", Cancel = "キャンセル", Ok = "OK", SelectLangTitle = "セットアップ言語の選択", SelectLangPrompt = "インストール中に使用する言語を選択してください:", Preparing = "インストール環境を準備しています...", Connecting = "サーバーに接続しています...", CreatingFolder = "インストール先フォルダーを作成しています...", ExtractingLocal = "ローカルパッケージから展開しています...", CopyingLocal = "ローカルコンポーネントをコピーしています...", Downloading = "クラウドからコンポーネントをダウンロードしています...", DownloadingChunk = "クラウドからコンポーネントをダウンロード中 ({0}/{1})...", ExtractingDeploying = "ファイルを展開して配置しています...", CreatingShortcuts = "ショートカットと関連付けを作成しています...", ShortcutsDetail = "デスクトップとスタートメニュー", Registering = "Windows システムに登録しています...", RegisterDetail = "Windows 設定からのアンインストールに対応", InstallDone = "インストール完了!", InstallDoneDetail = "すべてのコンポーネントの配置が完了し、使用可能です。", Finish = "完了", ErrorTitle = "インストールエラー", ErrorPrompt = "インストール中にエラーが発生しました:\n{0}\n\nヒント: 同じフォルダーに payload.zip を配置するとオフラインインストールが可能です。", UninstallWizardTitle = "{0} - アンインストール", UninstallHeaderTitle = "{0} のアンインストール", UninstallHeaderSub = "このアプリケーションとショートカットを完全に削除します", UninstallPrompt = "コンピューターから「{0}」を完全にアンインストールしますか?", UninstallDetail = "プログラムファイル、デスクトップおよびスタートメニューのショートカット、システム登録情報が削除されます。", UninstallAction = "アンインストール", UninstallProgressTitle = "アンインストールしています。しばらくお待ちください...", UninstallProgressDetail = "プロセスを終了し、ファイル、ショートカット、レジストリをクリーンアップしています...", UninstallDoneTitle = "アンインストール完了", UninstallDoneDetail = "「{0}」はコンピューターから完全に削除されました。ご利用ありがとうございました!" }); Add(new InstallLang { Code = "ko", DisplayName = "한국어 (Korean)", IsRtl = false, AppTitle = "YouTube 비디오 다운로더", WizardTitle = "{0} - 설치 마법사", HeaderSubtitle = "초경량 온라인 설치 프로그램 | 4K 비디오 및 MP3 오디오 지원", DestFolder = "설치 위치 폴더:", Browse = "찾아보기...", BrowseDesc = "YouTube 비디오 다운로더 설치 폴더 선택", ShortcutDesktop = "바탕화면에 바로가기 만들기", ShortcutStart = "시작 메뉴에 등록", LaunchAfter = "설치 완료 후 프로그램 실행", InstallNow = "지금 설치", Cancel = "취소", Ok = "확인", SelectLangTitle = "설치 언어 선택", SelectLangPrompt = "설치 중 사용할 언어를 선택하세요:", Preparing = "설치 환경을 준비하는 중입니다...", Connecting = "서버에 연결하는 중입니다...", CreatingFolder = "설치 폴더를 생성하는 중입니다...", ExtractingLocal = "로컬 오프라인 패키지에서 압축 해제 중...", CopyingLocal = "로컬 구성 요소를 복사하는 중...", Downloading = "클라우드에서 핵심 구성 요소를 다운로드하는 중...", DownloadingChunk = "클라우드에서 구성 요소 다운로드 중 ({0}/{1})...", ExtractingDeploying = "핵심 파일을 압축 해제하고 배치하는 중...", CreatingShortcuts = "바로가기 및 시스템 연결을 생성하는 중...", ShortcutsDetail = "바탕화면 및 시작 메뉴", Registering = "Windows 시스템에 애플리케이션 등록 중...", RegisterDetail = "Windows 설정에서 직접 제거 가능", InstallDone = "설치 완료!", InstallDoneDetail = "모든 구성 요소가 구성되었으며 사용할 준비가 되었습니다.", Finish = "완료", ErrorTitle = "설치 오류", ErrorPrompt = "설치 중 오류가 발생했습니다:\n{0}\n\n팁: 동일한 디렉터리에 payload.zip을 두면 오프라인으로 설치할 수 있습니다.", UninstallWizardTitle = "{0} - 제거 마법사", UninstallHeaderTitle = "{0} 제거", UninstallHeaderSub = "컴퓨터에서 이 응용 프로그램과 바로가기를 완전히 제거합니다" , UninstallPrompt = "컴퓨터에서 \"{0}\"을(를) 완전히 제거하시겠습니까?", UninstallDetail = "설치된 모든 프로그램 파일, 바로가기 및 시스템 설정 목록의 기록이 영구적으로 정리됩니다.", UninstallAction = "제거", UninstallProgressTitle = "제거하는 중입니다. 잠시 기다려 주세요...", UninstallProgressDetail = "프로세스를 종료하고 파일, 바로가기 및 레지스트리 항목을 정리하는 중입니다...", UninstallDoneTitle = "제거 완료", UninstallDoneDetail = "\"{0}\"이(가) 컴퓨터에서 완전히 제거되었습니다. 이용해 주셔서 감사합니다!" }); Add(new InstallLang { Code = "es", DisplayName = "Español (Spanish)", IsRtl = false, AppTitle = "Descargador de YouTube", WizardTitle = "{0} - Asistente de instalación", HeaderSubtitle = "Instalador ultraligero | Soporta vídeo 4K y audio MP3", DestFolder = "Carpeta de destino:", Browse = "Examinar...", BrowseDesc = "Seleccionar carpeta de instalación de Descargador de YouTube", ShortcutDesktop = "Crear acceso directo en el escritorio", ShortcutStart = "Crear acceso directo en el menú Inicio", LaunchAfter = "Iniciar la aplicación al completar la instalación", InstallNow = "Instalar ahora", Cancel = "Cancelar", Ok = "Aceptar", SelectLangTitle = "Seleccionar idioma de instalación", SelectLangPrompt = "Seleccione el idioma que se utilizará durante la instalación:", Preparing = "Preparando el entorno de instalación...", Connecting = "Conectando al servidor...", CreatingFolder = "Creando carpeta de instalación...", ExtractingLocal = "Extrayendo componentes del paquete local...", CopyingLocal = "Copiando componentes locales...", Downloading = "Descargando componentes principales desde la nube...", DownloadingChunk = "Descargando componentes ({0}/{1})...", ExtractingDeploying = "Extrayendo e implementando archivos principales...", CreatingShortcuts = "Creando accesos directos y asociaciones del sistema...", ShortcutsDetail = "Escritorio y menú Inicio", Registering = "Registrando la aplicación en Windows...", RegisterDetail = "Permite desinstalar desde Configuración de Windows", InstallDone = "¡Instalación completada!", InstallDoneDetail = "Todos los componentes se han configurado y están listos para usar.", Finish = "Finalizar", ErrorTitle = "Error de instalación", ErrorPrompt = "Ocurrió un error durante la instalación:\n{0}\n\nConsejo: Puede colocar payload.zip en el mismo directorio para instalación sin conexión.", UninstallWizardTitle = "{0} - Asistente de desinstalación", UninstallHeaderTitle = "Desinstalar {0}", UninstallHeaderSub = "Eliminar completamente esta aplicación y sus accesos directos del equipo", UninstallPrompt = "¿Está seguro de que desea desinstalar \"{0}\" de este equipo?", UninstallDetail = "Esto eliminará permanentemente todos los archivos del programa, accesos directos y entradas del sistema.", UninstallAction = "Desinstalar", UninstallProgressTitle = "Desinstalando, por favor espere...", UninstallProgressDetail = "Cerrando procesos y limpiando archivos, accesos directos y registros...", UninstallDoneTitle = "Desinstalación completada", UninstallDoneDetail = "\"{0}\" se ha eliminado por completo de su equipo. ¡Gracias por usarlo!" }); Add(new InstallLang { Code = "fr", DisplayName = "Français (French)", IsRtl = false, AppTitle = "Téléchargeur YouTube", WizardTitle = "{0} - Assistant d'installation", HeaderSubtitle = "Installateur ultra-léger | Prise en charge vidéo 4K et audio MP3", DestFolder = "Dossier de destination :", Browse = "Parcourir...", BrowseDesc = "Sélectionner le dossier d'installation de Téléchargeur YouTube", ShortcutDesktop = "Créer un raccourci sur le bureau", ShortcutStart = "Créer un raccourci dans le menu Démarrer", LaunchAfter = "Lancer l'application après l'installation", InstallNow = "Installer maintenant", Cancel = "Annuler", Ok = "OK", SelectLangTitle = "Sélectionner la langue de l'installation", SelectLangPrompt = "Sélectionnez la langue à utiliser pendant l'installation :", Preparing = "Préparation de l'environnement d'installation...", Connecting = "Connexion au serveur...", CreatingFolder = "Création du dossier d'installation...", ExtractingLocal = "Extraction des composants locaux...", CopyingLocal = "Copie des composants locaux...", Downloading = "Téléchargement des composants depuis le cloud...", DownloadingChunk = "Téléchargement des composants ({0}/{1})...", ExtractingDeploying = "Extraction et déploiement des fichiers...", CreatingShortcuts = "Création des raccourcis et associations système...", ShortcutsDetail = "Bureau et menu Démarrer", Registering = "Enregistrement de l'application dans Windows...", RegisterDetail = "Permet la désinstallation via les paramètres Windows", InstallDone = "Installation terminée !", InstallDoneDetail = "Tous les composants ont été configurés et sont prêts à être utilisés.", Finish = "Terminer", ErrorTitle = "Erreur d'installation", ErrorPrompt = "Une erreur est survenue pendant l'installation :\n{0}\n\nAstuce : Vous pouvez placer payload.zip dans le même dossier pour une installation hors ligne.", UninstallWizardTitle = "{0} - Assistant de désinstallation", UninstallHeaderTitle = "Désinstaller {0}", UninstallHeaderSub = "Supprimer complètement cette application et ses raccourcis de votre ordinateur", UninstallPrompt = "Voulez-vous vraiment désinstaller \"{0}\" de cet ordinateur ?", UninstallDetail = "Cette opération supprimera définitivement tous les fichiers du programme, les raccourcis et les entrées système.", UninstallAction = "Désinstaller", UninstallProgressTitle = "Désinstallation en cours, veuillez patienter...", UninstallProgressDetail = "Fermeture des processus et nettoyage des fichiers, raccourcis et entrées de registre...", UninstallDoneTitle = "Désinstallation terminée", UninstallDoneDetail = "\"{0}\" a été complètement supprimé de votre ordinateur. Merci de votre confiance !" }); Add(new InstallLang { Code = "de", DisplayName = "Deutsch (German)", IsRtl = false, AppTitle = "YouTube Video Downloader", WizardTitle = "{0} - Setup-Assistent", HeaderSubtitle = "Ultraleichter Online-Installer | Unterstützt 4K-Video & MP3-Audio", DestFolder = "Zielordner:", Browse = "Durchsuchen...", BrowseDesc = "Installationsordner für YouTube Video Downloader auswählen", ShortcutDesktop = "Desktop-Verknüpfung erstellen", ShortcutStart = "Startmenü-Verknüpfung erstellen", LaunchAfter = "Anwendung nach der Installation starten", InstallNow = "Jetzt installieren", Cancel = "Abbrechen", Ok = "OK", SelectLangTitle = "Setup-Sprache auswählen", SelectLangPrompt = "Wählen Sie die Sprache für die Installation aus:", Preparing = "Installationsumgebung wird vorbereitet...", Connecting = "Verbindung zum Server wird hergestellt...", CreatingFolder = "Installationsordner wird erstellt...", ExtractingLocal = "Komponenten aus lokalem Paket werden entpackt...", CopyingLocal = "Lokale Komponenten werden kopiert...", Downloading = "Kernkomponenten werden aus der Cloud heruntergeladen...", DownloadingChunk = "Komponenten werden heruntergeladen ({0}/{1})...", ExtractingDeploying = "Dateien werden entpackt und bereitgestellt...", CreatingShortcuts = "Verknüpfungen und Systemzuordnungen werden erstellt...", ShortcutsDetail = "Desktop und Startmenü", Registering = "Anwendung wird bei Windows registriert...", RegisterDetail = "Ermöglicht Deinstallation über Windows-Einstellungen", InstallDone = "Installation abgeschlossen!", InstallDoneDetail = "Alle Komponenten wurden konfiguriert und sind einsatzbereit.", Finish = "Fertigstellen", ErrorTitle = "Installationsfehler", ErrorPrompt = "Während der Installation ist ein Fehler aufgetreten:\n{0}\n\nTipp: Sie können payload.zip in denselben Ordner legen, um offline zu installieren.", UninstallWizardTitle = "{0} - Deinstallations-Assistent", UninstallHeaderTitle = "{0} deinstallieren", UninstallHeaderSub = "Entfernt diese Anwendung und Verknüpfungen vollständig von Ihrem Computer", UninstallPrompt = "Möchten Sie \"{0}\" wirklich von diesem Computer deinstallieren?", UninstallDetail = "Dadurch werden alle Programmdateien, Verknüpfungen und Systemeinträge dauerhaft bereinigt.", UninstallAction = "Deinstallieren", UninstallProgressTitle = "Deinstallation läuft, bitte warten...", UninstallProgressDetail = "Prozesse werden beendet, Dateien, Verknüpfungen und Registrierungseinträge werden bereinigt...", UninstallDoneTitle = "Deinstallation abgeschlossen", UninstallDoneDetail = "\"{0}\" wurde vollständig von Ihrem Computer entfernt. Vielen Dank für die Nutzung!" }); Add(new InstallLang { Code = "ru", DisplayName = "Русский (Russian)", IsRtl = false, AppTitle = "Загрузчик видео с YouTube", WizardTitle = "{0} - Мастер установки", HeaderSubtitle = "Легкий онлайн-установщик | Поддержка 4K видео и MP3 аудио", DestFolder = "Папка назначения:", Browse = "Обзор...", BrowseDesc = "Выберите папку для установки Загрузчика видео с YouTube", ShortcutDesktop = "Создать ярлык на рабочем столе", ShortcutStart = "Создать ярлык в меню «Пуск»", LaunchAfter = "Запустить программу после завершения установки", InstallNow = "Установить сейчас", Cancel = "Отмена", Ok = "ОК", SelectLangTitle = "Выбор языка установки", SelectLangPrompt = "Выберите язык для процесса установки:", Preparing = "Подготовка среды установки...", Connecting = "Подключение к серверу...", CreatingFolder = "Создание папки установки...", ExtractingLocal = "Распаковка компонентов из локального архива...", CopyingLocal = "Копирование локальных компонентов...", Downloading = "Загрузка компонентов из облака...", DownloadingChunk = "Загрузка компонентов ({0}/{1})...", ExtractingDeploying = "Распаковка и развертывание файлов...", CreatingShortcuts = "Создание ярлыков и ассоциаций системы...", ShortcutsDetail = "Рабочий стол и меню «Пуск»", Registering = "Регистрация приложения в Windows...", RegisterDetail = "Поддержка удаления через параметры Windows", InstallDone = "Установка завершена!", InstallDoneDetail = "Все компоненты настроены и готовы к использованию.", Finish = "Завершить", ErrorTitle = "Ошибка установки", ErrorPrompt = "Во время установки произошла ошибка:\n{0}\n\nПодсказка: Вы можете поместить payload.zip в ту же папку для офлайн-установки.", UninstallWizardTitle = "{0} - Мастер удаления", UninstallHeaderTitle = "Удаление {0}", UninstallHeaderSub = "Полное удаление этого приложения и ярлыков с вашего компьютера", UninstallPrompt = "Вы действительно хотите удалить «{0}» с этого компьютера?", UninstallDetail = "Это действие безвозвратно удалит все файлы программы, ярлыки и записи в реестре системы.", UninstallAction = "Удалить", UninstallProgressTitle = "Идет удаление, пожалуйста подождите...", UninstallProgressDetail = "Завершение процессов и очистка файлов, ярлыков и записей реестра...", UninstallDoneTitle = "Удаление завершено", UninstallDoneDetail = "«{0}» успешно удален с вашего компьютера. Спасибо за использование!" }); Add(new InstallLang { Code = "pt", DisplayName = "Português (Portuguese)", IsRtl = false, AppTitle = "Baixador do YouTube", WizardTitle = "{0} - Assistente de Instalação", HeaderSubtitle = "Instalador online ultraleve | Suporte a vídeo 4K e áudio MP3", DestFolder = "Pasta de destino:", Browse = "Procurar...", BrowseDesc = "Selecione a pasta de instalação do Baixador do YouTube", ShortcutDesktop = "Criar atalho na área de trabalho", ShortcutStart = "Criar atalho no menu Iniciar", LaunchAfter = "Iniciar o aplicativo após a instalação", InstallNow = "Instalar Agora", Cancel = "Cancelar", Ok = "OK", SelectLangTitle = "Selecionar Idioma de Instalação", SelectLangPrompt = "Selecione o idioma a ser usado durante a instalação:", Preparing = "Preparando o ambiente de instalação...", Connecting = "Conectando ao servidor...", CreatingFolder = "Criando pasta de instalação...", ExtractingLocal = "Extraindo componentes do pacote local...", CopyingLocal = "Copiando componentes locais...", Downloading = "Baixando componentes principais da nuvem...", DownloadingChunk = "Baixando componentes ({0}/{1})...", ExtractingDeploying = "Extraindo e implantando arquivos principais...", CreatingShortcuts = "Criando atalhos e associações do sistema...", ShortcutsDetail = "Área de trabalho e Menu Iniciar", Registering = "Registrando o aplicativo no Windows...", RegisterDetail = "Permite desinstalação pelas Configurações do Windows", InstallDone = "Instalação Concluída!", InstallDoneDetail = "Todos os componentes foram configurados e estão prontos para uso.", Finish = "Concluir", ErrorTitle = "Erro de Instalação", ErrorPrompt = "Ocorreu um erro durante a instalação:\n{0}\n\nDica: Você pode colocar o arquivo payload.zip no mesmo diretório para instalação offline.", UninstallWizardTitle = "{0} - Assistente de Desinstalação", UninstallHeaderTitle = "Desinstalar {0}", UninstallHeaderSub = "Remover completamente este aplicativo e atalhos do seu computador", UninstallPrompt = "Tem certeza de que deseja desinstalar \"{0}\" deste computador?", UninstallDetail = "Isso removerá permanentemente todos os arquivos do programa, atalhos e registros das configurações do sistema.", UninstallAction = "Desinstalar", UninstallProgressTitle = "Desinstalando, por favor aguarde...", UninstallProgressDetail = "Encerrando processos e limpando arquivos, atalhos e entradas do registro...", UninstallDoneTitle = "Desinstalação Concluída", UninstallDoneDetail = "\"{0}\" foi removido com sucesso do seu computador. Obrigado por usar!" }); Add(new InstallLang { Code = "it", DisplayName = "Italiano (Italian)", IsRtl = false, AppTitle = "Downloader per YouTube", WizardTitle = "{0} - Installazione guidata", HeaderSubtitle = "Installer online ultraleggero | Supporta video 4K e audio MP3", DestFolder = "Cartella di destinazione:", Browse = "Sfoglia...", BrowseDesc = "Seleziona la cartella di installazione di Downloader per YouTube", ShortcutDesktop = "Crea collegamento sul desktop", ShortcutStart = "Crea collegamento nel menu Start", LaunchAfter = "Avvia l'applicazione al termine dell'installazione", InstallNow = "Installa ora", Cancel = "Annulla", Ok = "OK", SelectLangTitle = "Seleziona lingua di installazione", SelectLangPrompt = "Seleziona la lingua da utilizzare durante l'installazione:", Preparing = "Preparazione dell'ambiente di installazione...", Connecting = "Connessione al server...", CreatingFolder = "Creazione della cartella di installazione...", ExtractingLocal = "Estrazione dei componenti dal pacchetto locale...", CopyingLocal = "Copia dei componenti locali...", Downloading = "Download dei componenti principali dal cloud...", DownloadingChunk = "Download dei componenti ({0}/{1})...", ExtractingDeploying = "Estrazione e distribuzione dei file...", CreatingShortcuts = "Creazione di collegamenti e associazioni di sistema...", ShortcutsDetail = "Desktop e Menu Start", Registering = "Registrazione dell'applicazione in Windows...", RegisterDetail = "Supporta la disinstallazione da Impostazioni di Windows", InstallDone = "Installazione completata!", InstallDoneDetail = "Tutti i componenti sono stati configurati e sono pronti all'uso.", Finish = "Fine", ErrorTitle = "Errore di installazione", ErrorPrompt = "Si è verificato un errore durante l'installazione:\n{0}\n\nSuggerimento: È possibile inserire payload.zip nella stessa cartella per l'installazione offline.", UninstallWizardTitle = "{0} - Disinstallazione guidata", UninstallHeaderTitle = "Disinstalla {0}", UninstallHeaderSub = "Rimuovi completamente questa applicazione e i suoi collegamenti dal computer", UninstallPrompt = "Sei sicuro di voler disinstallare \"{0}\" da questo computer?", UninstallDetail = "Questa operazione rimuoverà definitivamente tutti i file del programma, i collegamenti e le voci di sistema.", UninstallAction = "Disinstalla", UninstallProgressTitle = "Disinstallazione in corso, attendere...", UninstallProgressDetail = "Chiusura dei processi e pulizia dei file, dei collegamenti e delle voci di registro...", UninstallDoneTitle = "Disinstallazione completata", UninstallDoneDetail = "\"{0}\" è stato rimosso completamente dal computer. Grazie per averlo utilizzato!" }); Add(new InstallLang { Code = "ar", DisplayName = "العربية (Arabic)", IsRtl = true, AppTitle = "أداة تنزيل يوتيوب", WizardTitle = "{0} - معالج التثبيت", HeaderSubtitle = "مثبت فائق الخفة عبر الإنترنت | يدعم فيديو 4K وصوت MP3", DestFolder = "مجلد التثبيت:", Browse = "استعراض...", BrowseDesc = "حدد مجلد تثبيت أداة تنزيل يوتيوب", ShortcutDesktop = "إنشاء اختصار على سطح المكتب", ShortcutStart = "إنشاء اختصار في قائمة ابدأ", LaunchAfter = "تشغيل التطبيق فور اكتمال التثبيت", InstallNow = "تثبيت الآن", Cancel = "إلغاء", Ok = "موافق", SelectLangTitle = "اختيار لغة التثبيت", SelectLangPrompt = "يرجى تحديد اللغة التي تريد استخدامها أثناء التثبيت:", Preparing = "جارٍ تجهيز بيئة التثبيت...", Connecting = "جارٍ الاتصال بالخادم...", CreatingFolder = "جارٍ إنشاء مجلد التثبيت...", ExtractingLocal = "جارٍ استخراج المكونات من الحزمة المحلية...", CopyingLocal = "جارٍ نسخ المكونات المحلية...", Downloading = "جارٍ تنزيل المكونات الأساسية من السحابة...", DownloadingChunk = "جارٍ تنزيل المكونات ({0}/{1})...", ExtractingDeploying = "جارٍ استخراج وتوزيع الملفات الأساسية...", CreatingShortcuts = "جارٍ إنشاء الاختصارات وربط النظام...", ShortcutsDetail = "سطح المكتب وقائمة ابدأ", Registering = "جارٍ تسجيل التطبيق في نظام Windows...", RegisterDetail = "يدعم إلغاء التثبيت المباشر من إعدادات Windows", InstallDone = "اكتمل التثبيت!", InstallDoneDetail = "تم تكوين جميع المكونات وهي جاهزة للاستخدام الآن.", Finish = "إنهاء", ErrorTitle = "خطأ في التثبيت", ErrorPrompt = "حدث خطأ أثناء التثبيت:\n{0}\n\nتلميح: يمكنك وضع payload.zip في نفس الدليل للتثبيت دون إنترنت.", UninstallWizardTitle = "{0} - معالج إلغاء التثبيت", UninstallHeaderTitle = "إلغاء تثبيت {0}", UninstallHeaderSub = "إزالة هذا التطبيق واختصاراته تمامًا من جهاز الكمبيوتر الخاص بك", UninstallPrompt = "هل أنت متأكد من رغبتك في إلغاء تثبيت \"{0}\" بالكامل من هذا الكمبيوتر؟", UninstallDetail = "ستؤدي هذه العملية إلى إزالة جميع ملفات البرنامج والاختصارات وسجلات إعدادات النظام نهائيًا.", UninstallAction = "إلغاء التثبيت", UninstallProgressTitle = "جارٍ إلغاء التثبيت، يرجى الانتظار...", UninstallProgressDetail = "جارٍ إنهاء العمليات وتنظيف الملفات والاختصارات وإدخالات السجل...", UninstallDoneTitle = "اكتمل إلغاء التثبيت", UninstallDoneDetail = "تمت إزالة \"{0}\" تمامًا من جهاز الكمبيوتر الخاص بك. شكرًا لك على استخدامه!" }); } private static void Add(InstallLang l) { _langs[l.Code] = l; All.Add(l); } public static InstallLang Get(string code) { if (string.IsNullOrEmpty(code)) return _langs["en"]; InstallLang lang; if (_langs.TryGetValue(code, out lang)) return lang; if (code.Contains("-")) { string prefix = code.Split('-')[0]; foreach (var kv in _langs) { if (kv.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return kv.Value; } } return _langs["en"]; } public static string DetectLanguageCode() { string uiName = ""; try { uiName = System.Globalization.CultureInfo.CurrentUICulture.Name; } catch {} if (string.IsNullOrEmpty(uiName)) { try { uiName = System.Globalization.CultureInfo.InstalledUICulture.Name; } catch {} } if (string.IsNullOrEmpty(uiName)) return "en"; uiName = uiName.ToLowerInvariant(); if (uiName.StartsWith("zh-tw") || uiName.StartsWith("zh-hk") || uiName.StartsWith("zh-mo") || uiName.Contains("hant")) return "zh-TW"; if (uiName.StartsWith("zh")) return "zh-CN"; if (uiName.StartsWith("ja")) return "ja"; if (uiName.StartsWith("ko")) return "ko"; if (uiName.StartsWith("es")) return "es"; if (uiName.StartsWith("fr")) return "fr"; if (uiName.StartsWith("de")) return "de"; if (uiName.StartsWith("ru")) return "ru"; if (uiName.StartsWith("pt")) return "pt"; if (uiName.StartsWith("it")) return "it"; if (uiName.StartsWith("ar")) return "ar"; return "en"; } public static InstallLang GetSavedOrDetectedLanguage() { string code = null; try { string curDir = AppDomain.CurrentDomain.BaseDirectory; string iniFile = Path.Combine(curDir, "setup_config.ini"); if (!File.Exists(iniFile)) { string defDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Programs", "YouTube Downloader"); iniFile = Path.Combine(defDir, "setup_config.ini"); } if (File.Exists(iniFile)) { foreach (string line in File.ReadAllLines(iniFile)) { if (line.StartsWith("lang=", StringComparison.OrdinalIgnoreCase)) { code = line.Substring(5).Trim(); break; } } } } catch {} if (string.IsNullOrEmpty(code)) code = DetectLanguageCode(); return Get(code); } } class Program { public static string CustomUrl = null; public static string CustomLang = null; [STAThread] static void Main(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // 啟用 TLS 1.2 及 TLS 1.3 try { ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; } catch {} string exeName = Path.GetFileNameWithoutExtension(AppDomain.CurrentDomain.FriendlyName); bool isUninstall = exeName.IndexOf("uninstall", StringComparison.OrdinalIgnoreCase) >= 0; foreach (string arg in args) { if (arg.Equals("/UNINSTALL", StringComparison.OrdinalIgnoreCase) || arg.Equals("--uninstall", StringComparison.OrdinalIgnoreCase) || arg.Equals("-uninstall", StringComparison.OrdinalIgnoreCase)) { isUninstall = true; } else if (arg.StartsWith("/URL=", StringComparison.OrdinalIgnoreCase) || arg.StartsWith("--url=", StringComparison.OrdinalIgnoreCase)) { int eq = arg.IndexOf('='); if (eq > 0 && eq < arg.Length - 1) { CustomUrl = arg.Substring(eq + 1).Trim('"', '\''); } } else if (arg.StartsWith("/LANG=", StringComparison.OrdinalIgnoreCase) || arg.StartsWith("--lang=", StringComparison.OrdinalIgnoreCase)) { int eq = arg.IndexOf('='); if (eq > 0 && eq < arg.Length - 1) { CustomLang = arg.Substring(eq + 1).Trim('"', '\''); } } } if (isUninstall) { InstallLang uLang = null; if (!string.IsNullOrEmpty(CustomLang)) uLang = I18n.Get(CustomLang); else uLang = I18n.GetSavedOrDetectedLanguage(); Application.Run(new ModernUninstaller(uLang)); } else { InstallLang selectedLang = null; using (var dlg = new LanguageSelectDialog(CustomLang)) { if (dlg.ShowDialog() != DialogResult.OK) { return; // 使用者按取消或關閉 } selectedLang = dlg.SelectedLanguage; } if (selectedLang == null) selectedLang = I18n.Get(I18n.DetectLanguageCode()); Application.Run(new ModernInstaller(selectedLang)); } } public static void CreateLnk(string lnk, string target, string icon, string dir) { try { Type t = Type.GetTypeFromProgID("WScript.Shell"); dynamic s = Activator.CreateInstance(t); dynamic c = s.CreateShortcut(lnk); c.TargetPath = target; c.WorkingDirectory = dir; if (File.Exists(icon)) c.IconLocation = icon + ",0"; c.Save(); } catch {} } public static void RegisterApp(string targetDir, string targetIcon, string targetSetup, string appName) { try { using (Microsoft.Win32.RegistryKey key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\YouTubeDownloader")) { if (key != null) { key.SetValue("DisplayName", appName); key.SetValue("DisplayVersion", "1.2.0"); key.SetValue("Publisher", "Ryan"); key.SetValue("DisplayIcon", targetIcon); key.SetValue("InstallLocation", targetDir); key.SetValue("UninstallString", "\"" + targetSetup + "\" /UNINSTALL"); key.SetValue("QuietUninstallString", "\"" + targetSetup + "\" /UNINSTALL"); key.SetValue("EstimatedSize", 42000, Microsoft.Win32.RegistryValueKind.DWord); key.SetValue("NoModify", 1, Microsoft.Win32.RegistryValueKind.DWord); key.SetValue("NoRepair", 1, Microsoft.Win32.RegistryValueKind.DWord); } } } catch {} } public static void RegDel(string subKey) { try { Microsoft.Win32.Registry.CurrentUser.DeleteSubKeyTree(subKey, false); } catch {} } } public class LanguageSelectDialog : Form { private PictureBox picIcon; private Label lblPrompt; private ComboBox cmbLangs; private Button btnOk; private Button btnCancel; public InstallLang SelectedLanguage { get; private set; } public LanguageSelectDialog(string preselectCode = null) { this.Size = new Size(430, 200); this.StartPosition = FormStartPosition.CenterScreen; this.FormBorderStyle = FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.MinimizeBox = false; this.ShowInTaskbar = true; this.BackColor = Color.FromArgb(249, 250, 252); this.Font = new Font("Microsoft JhengHei UI", 9F); this.TopMost = true; try { var asm = Assembly.GetExecutingAssembly(); using (Stream sIco = asm.GetManifestResourceStream("app_icon.ico")) { if (sIco != null) this.Icon = new Icon(sIco); } } catch {} picIcon = new PictureBox { Size = new Size(42, 42), Location = new Point(22, 22), SizeMode = PictureBoxSizeMode.Zoom }; try { var asm = Assembly.GetExecutingAssembly(); using (Stream sLogo = asm.GetManifestResourceStream("app_logo.png")) { if (sLogo != null) picIcon.Image = Image.FromStream(sLogo); } } catch {} this.Controls.Add(picIcon); lblPrompt = new Label { Location = new Point(78, 20), Size = new Size(320, 36), ForeColor = Color.FromArgb(31, 41, 55), Font = new Font("Microsoft JhengHei UI", 9F) }; this.Controls.Add(lblPrompt); cmbLangs = new ComboBox { Location = new Point(78, 60), Size = new Size(314, 26), DropDownStyle = ComboBoxStyle.DropDownList, Font = new Font("Microsoft JhengHei UI", 9.5F), BackColor = Color.White, ForeColor = Color.FromArgb(31, 41, 55) }; foreach (var l in I18n.All) { cmbLangs.Items.Add(l); } string targetCode = preselectCode; if (string.IsNullOrEmpty(targetCode)) targetCode = I18n.DetectLanguageCode(); int selectIdx = 0; for (int i = 0; i < cmbLangs.Items.Count; i++) { var l = (InstallLang)cmbLangs.Items[i]; if (l.Code.Equals(targetCode, StringComparison.OrdinalIgnoreCase)) { selectIdx = i; break; } } cmbLangs.SelectedIndex = selectIdx; cmbLangs.SelectedIndexChanged += (s, e) => UpdateDialogLang(); this.Controls.Add(cmbLangs); btnOk = new Button { Size = new Size(88, 30), Location = new Point(216, 110), FlatStyle = FlatStyle.Flat, BackColor = Color.FromArgb(0, 103, 192), ForeColor = Color.White, Cursor = Cursors.Hand, Font = new Font("Microsoft JhengHei UI", 9F, FontStyle.Bold) }; btnOk.FlatAppearance.BorderSize = 0; btnOk.Click += (s, e) => { this.SelectedLanguage = (InstallLang)cmbLangs.SelectedItem; this.DialogResult = DialogResult.OK; this.Close(); }; this.Controls.Add(btnOk); btnCancel = new Button { Size = new Size(88, 30), Location = new Point(312, 110), FlatStyle = FlatStyle.Flat, BackColor = Color.White, ForeColor = Color.FromArgb(55, 65, 81), Cursor = Cursors.Hand }; btnCancel.FlatAppearance.BorderColor = Color.FromArgb(209, 213, 219); btnCancel.Click += (s, e) => { this.DialogResult = DialogResult.Cancel; this.Close(); }; this.Controls.Add(btnCancel); this.AcceptButton = btnOk; this.CancelButton = btnCancel; UpdateDialogLang(); } private void UpdateDialogLang() { var lang = (InstallLang)cmbLangs.SelectedItem; if (lang == null) return; this.SelectedLanguage = lang; this.Text = lang.SelectLangTitle; lblPrompt.Text = lang.SelectLangPrompt; btnOk.Text = lang.Ok; btnCancel.Text = lang.Cancel; if (lang.IsRtl) { this.RightToLeft = RightToLeft.Yes; this.RightToLeftLayout = true; } else { this.RightToLeft = RightToLeft.No; this.RightToLeftLayout = false; } } } public class ModernInstaller : Form { // 預設 Cloudflare Pages 下載端點 (已對接專案網址) public const string DEFAULT_CF_URL = "https://youtube-downloader-7g5.pages.dev/assets/"; private readonly InstallLang _lang; private Panel panelHeader; private Panel panelContent; private Panel panelFooter; private PictureBox picIcon; private Label lblTitle; private Label lblSub; // View 1: Welcome & Settings private Panel pnlWelcome; private Label lblDest; private TextBox txtPath; private Button btnBrowse; private CheckBox chkDesktop; private CheckBox chkStart; private CheckBox chkLaunch; // View 2: Progress private Panel pnlProgress; private Label lblProgTitle; private ProgressBar progressBar; private Label lblProgDetail; // View 3: Complete private Panel pnlDone; private Label lblDoneTitle; private Label lblDoneDesc; private Button btnAction; private Button btnCancel; public ModernInstaller(InstallLang lang = null) { _lang = lang ?? I18n.Get(I18n.DetectLanguageCode()); this.Text = string.Format(_lang.WizardTitle, _lang.AppTitle); this.Size = new Size(540, 360); this.StartPosition = FormStartPosition.CenterScreen; this.FormBorderStyle = FormBorderStyle.FixedSingle; this.MaximizeBox = false; this.BackColor = Color.FromArgb(249, 250, 252); this.Font = new Font("Microsoft JhengHei UI", 9F); if (_lang.IsRtl) { this.RightToLeft = RightToLeft.Yes; this.RightToLeftLayout = true; } // Header panelHeader = new Panel { Dock = DockStyle.Top, Height = 84, BackColor = Color.White }; panelHeader.Paint += (s, e) => { using (Pen p = new Pen(Color.FromArgb(229, 231, 235), 1)) { e.Graphics.DrawLine(p, 0, 83, panelHeader.Width, 83); } }; picIcon = new PictureBox { Size = new Size(52, 52), Location = new Point(24, 16), SizeMode = PictureBoxSizeMode.Zoom }; try { var asm = Assembly.GetExecutingAssembly(); using (Stream sLogo = asm.GetManifestResourceStream("app_logo.png")) { if (sLogo != null) picIcon.Image = Image.FromStream(sLogo); } using (Stream sIco = asm.GetManifestResourceStream("app_icon.ico")) { if (sIco != null) this.Icon = new Icon(sIco); } } catch {} lblTitle = new Label { Text = _lang.AppTitle, Font = new Font("Microsoft JhengHei UI", 13.5F, FontStyle.Bold), ForeColor = Color.FromArgb(24, 24, 27), Location = new Point(88, 16), AutoSize = true }; lblSub = new Label { Text = "v1.2.0 | " + _lang.HeaderSubtitle, Font = new Font("Microsoft JhengHei UI", 8.8F), ForeColor = Color.FromArgb(113, 113, 122), Location = new Point(89, 46), AutoSize = true }; panelHeader.Controls.Add(picIcon); panelHeader.Controls.Add(lblTitle); panelHeader.Controls.Add(lblSub); // Footer panelFooter = new Panel { Dock = DockStyle.Bottom, Height = 64, BackColor = Color.FromArgb(244, 245, 247) }; panelFooter.Paint += (s, e) => { using (Pen p = new Pen(Color.FromArgb(229, 231, 235), 1)) { e.Graphics.DrawLine(p, 0, 0, panelFooter.Width, 0); } }; btnCancel = new Button { Text = _lang.Cancel, Size = new Size(88, 34), Location = new Point(426, 15), FlatStyle = FlatStyle.Flat, BackColor = Color.White, ForeColor = Color.FromArgb(55, 65, 81), Cursor = Cursors.Hand }; btnCancel.FlatAppearance.BorderColor = Color.FromArgb(209, 213, 219); btnCancel.Click += (s, e) => this.Close(); btnAction = new Button { Text = _lang.InstallNow, Size = new Size(110, 34), Location = new Point(306, 15), FlatStyle = FlatStyle.Flat, BackColor = Color.FromArgb(0, 103, 192), ForeColor = Color.White, Cursor = Cursors.Hand, Font = new Font("Microsoft JhengHei UI", 9F, FontStyle.Bold) }; btnAction.FlatAppearance.BorderSize = 0; btnAction.Click += BtnAction_Click; panelFooter.Controls.Add(btnCancel); panelFooter.Controls.Add(btnAction); // Content panelContent = new Panel { Dock = DockStyle.Fill, BackColor = Color.FromArgb(249, 250, 252) }; // ------------------ View 1: Welcome & Settings ------------------ pnlWelcome = new Panel { Dock = DockStyle.Fill }; lblDest = new Label { Text = _lang.DestFolder, Font = new Font("Microsoft JhengHei UI", 9F, FontStyle.Bold), ForeColor = Color.FromArgb(55, 65, 81), Location = new Point(28, 16), AutoSize = true }; pnlWelcome.Controls.Add(lblDest); string defPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Programs", "YouTube Downloader"); txtPath = new TextBox { Text = defPath, Location = new Point(30, 40), Size = new Size(385, 23), BackColor = Color.White, BorderStyle = BorderStyle.FixedSingle }; pnlWelcome.Controls.Add(txtPath); btnBrowse = new Button { Text = _lang.Browse, Location = new Point(423, 39), Size = new Size(77, 25), FlatStyle = FlatStyle.Flat, BackColor = Color.White, Cursor = Cursors.Hand }; btnBrowse.FlatAppearance.BorderColor = Color.FromArgb(209, 213, 219); btnBrowse.Click += (s, e) => { using (FolderBrowserDialog fbd = new FolderBrowserDialog()) { fbd.Description = _lang.BrowseDesc; fbd.SelectedPath = txtPath.Text; if (fbd.ShowDialog() == DialogResult.OK) { txtPath.Text = fbd.SelectedPath; } } }; pnlWelcome.Controls.Add(btnBrowse); chkDesktop = new CheckBox { Text = _lang.ShortcutDesktop, Checked = true, Location = new Point(30, 80), AutoSize = true, Cursor = Cursors.Hand }; chkStart = new CheckBox { Text = _lang.ShortcutStart, Checked = true, Location = new Point(30, 108), AutoSize = true, Cursor = Cursors.Hand }; chkLaunch = new CheckBox { Text = _lang.LaunchAfter, Checked = true, Location = new Point(30, 136), AutoSize = true, Cursor = Cursors.Hand }; pnlWelcome.Controls.Add(chkDesktop); pnlWelcome.Controls.Add(chkStart); pnlWelcome.Controls.Add(chkLaunch); // ------------------ View 2: Progress ------------------ pnlProgress = new Panel { Dock = DockStyle.Fill, Visible = false }; lblProgTitle = new Label { Text = _lang.Preparing, Font = new Font("Microsoft JhengHei UI", 10.5F, FontStyle.Bold), ForeColor = Color.FromArgb(31, 41, 55), Location = new Point(28, 40), AutoSize = true }; progressBar = new ProgressBar { Location = new Point(30, 75), Size = new Size(470, 22), Style = ProgressBarStyle.Continuous, Value = 0 }; lblProgDetail = new Label { Text = _lang.Connecting, Font = new Font("Microsoft JhengHei UI", 8.5F), ForeColor = Color.FromArgb(107, 114, 128), Location = new Point(30, 108), Size = new Size(470, 45) }; pnlProgress.Controls.Add(lblProgTitle); pnlProgress.Controls.Add(progressBar); pnlProgress.Controls.Add(lblProgDetail); // ------------------ View 3: Complete ------------------ pnlDone = new Panel { Dock = DockStyle.Fill, Visible = false }; PictureBox picDone = new PictureBox { Size = new Size(48, 48), Location = new Point(30, 36), SizeMode = PictureBoxSizeMode.Zoom }; try { var asm = Assembly.GetExecutingAssembly(); using (Stream sLogo = asm.GetManifestResourceStream("app_logo.png")) { if (sLogo != null) picDone.Image = Image.FromStream(sLogo); } } catch {} lblDoneTitle = new Label { Text = _lang.InstallDone, Font = new Font("Microsoft JhengHei UI", 12F, FontStyle.Bold), ForeColor = Color.FromArgb(22, 101, 52), Location = new Point(90, 36), AutoSize = true }; lblDoneDesc = new Label { Text = _lang.InstallDoneDetail, Font = new Font("Microsoft JhengHei UI", 9F), ForeColor = Color.FromArgb(75, 85, 99), Location = new Point(92, 66), Size = new Size(410, 45) }; pnlDone.Controls.Add(picDone); pnlDone.Controls.Add(lblDoneTitle); pnlDone.Controls.Add(lblDoneDesc); panelContent.Controls.Add(pnlWelcome); panelContent.Controls.Add(pnlProgress); panelContent.Controls.Add(pnlDone); this.Controls.Add(panelContent); this.Controls.Add(panelHeader); this.Controls.Add(panelFooter); } private void UpdateProgress(int pct, string title, string detail) { if (this.InvokeRequired) { this.BeginInvoke((Action)(() => UpdateProgress(pct, title, detail))); return; } if (pct < 0) pct = 0; if (pct > 100) pct = 100; progressBar.Value = pct; if (!string.IsNullOrEmpty(title)) lblProgTitle.Text = title; if (!string.IsNullOrEmpty(detail)) lblProgDetail.Text = detail; } private void BtnAction_Click(object sender, EventArgs e) { string targetDir = txtPath.Text.Trim(); if (string.IsNullOrEmpty(targetDir)) { MessageBox.Show(this, "請指定有效的安裝資料夾路徑。", "路徑錯誤", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } if (btnAction.Text == _lang.Finish) { if (chkLaunch.Checked) { string exe = Path.Combine(targetDir, "YouTube_Downloader.exe"); if (File.Exists(exe)) { try { Process.Start(new ProcessStartInfo(exe) { WorkingDirectory = targetDir }); } catch {} } } this.Close(); return; } pnlWelcome.Visible = false; pnlProgress.Visible = true; btnAction.Enabled = false; btnCancel.Enabled = false; string serverUrl = GetServerUrl(); bool createDesk = chkDesktop.Checked; bool createStart = chkStart.Checked; System.Threading.ThreadPool.QueueUserWorkItem((state) => { try { ExecuteInstall(targetDir, serverUrl, createDesk, createStart); } catch (Exception ex) { this.BeginInvoke((Action)(() => { pnlProgress.Visible = false; pnlWelcome.Visible = true; btnAction.Enabled = true; btnCancel.Enabled = true; MessageBox.Show(this, string.Format(_lang.ErrorPrompt, ex.Message), _lang.ErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); })); } }); } private string GetServerUrl() { string url = Program.CustomUrl; if (string.IsNullOrEmpty(url)) { string cfgFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "setup_config.ini"); if (File.Exists(cfgFile)) { try { foreach (string line in File.ReadAllLines(cfgFile)) { if (line.StartsWith("url=", StringComparison.OrdinalIgnoreCase)) { url = line.Substring(4).Trim(); break; } } } catch {} } } if (string.IsNullOrEmpty(url)) url = DEFAULT_CF_URL; return url.Trim().TrimEnd('/') + "/"; } private static void ExtractZipSafe(string zipPath, string targetDir) { using (var archive = ZipFile.OpenRead(zipPath)) { foreach (var entry in archive.Entries) { string fullPath = Path.Combine(targetDir, entry.FullName); string entryDir = Path.GetDirectoryName(fullPath); if (!string.IsNullOrEmpty(entryDir) && !Directory.Exists(entryDir)) Directory.CreateDirectory(entryDir); if (!string.IsNullOrEmpty(entry.Name)) entry.ExtractToFile(fullPath, true); } } } private void ExecuteInstall(string targetDir, string serverUrl, bool createDesk, bool createStart) { try { foreach (var proc in Process.GetProcessesByName("YouTube_Downloader")) { try { proc.Kill(); proc.WaitForExit(1000); } catch {} } } catch {} UpdateProgress(5, _lang.CreatingFolder, targetDir); if (!Directory.Exists(targetDir)) Directory.CreateDirectory(targetDir); string targetExe = Path.Combine(targetDir, "YouTube_Downloader.exe"); string targetIcon = Path.Combine(targetDir, "app_icon.ico"); string targetSetup = Path.Combine(targetDir, "YouTube_Downloader_Setup.exe"); string appName = _lang.AppTitle; string localDir = AppDomain.CurrentDomain.BaseDirectory; string localZip = Path.Combine(localDir, "payload.zip"); string localExe = Path.Combine(localDir, "YouTube_Downloader.exe"); bool acquired = false; // 1. 優先檢查本機離線檔案 if (File.Exists(localZip)) { UpdateProgress(30, _lang.ExtractingLocal, localZip); ExtractZipSafe(localZip, targetDir); acquired = true; } else if (File.Exists(localExe)) { UpdateProgress(30, _lang.CopyingLocal, localExe); File.Copy(localExe, targetExe, true); string localIco = Path.Combine(localDir, "app_icon.ico"); if (File.Exists(localIco)) File.Copy(localIco, targetIcon, true); acquired = true; } // 2. 若本機無檔案,則透過 Cloudflare Pages 雲端 CDN 自動下載 if (!acquired) { UpdateProgress(10, _lang.Connecting, serverUrl); string tempZip = Path.Combine(Path.GetTempPath(), "yt_payload_" + Guid.NewGuid().ToString("N") + ".zip"); try { using (WebClient wc = new WebClient()) { wc.Headers.Add("User-Agent", "YouTubeDownloaderSetup/1.0"); // 檢查是否存在 manifest.json (分段壓縮包架構) string manifestUrl = serverUrl + "manifest.json"; string manifestJson = null; try { manifestJson = wc.DownloadString(manifestUrl); } catch {} List chunkFiles = new List(); long totalExpectedSize = 0; if (!string.IsNullOrEmpty(manifestJson)) { var chunkMatches = Regex.Matches(manifestJson, "\"name\"\\s*:\\s*\"([^\"]+)\""); foreach (Match m in chunkMatches) { chunkFiles.Add(m.Groups[1].Value); } var sizeMatch = Regex.Match(manifestJson, "\"total_size\"\\s*:\\s*(\\d+)"); if (sizeMatch.Success) long.TryParse(sizeMatch.Groups[1].Value, out totalExpectedSize); } if (chunkFiles.Count > 0) { // 分段平行/依序下載並合併 using (FileStream fsOut = new FileStream(tempZip, FileMode.Create, FileAccess.Write)) { for (int i = 0; i < chunkFiles.Count; i++) { string cName = chunkFiles[i]; string cUrl = serverUrl + cName; int curIdx = i + 1; int totIdx = chunkFiles.Count; int basePct = 10 + (int)(70.0 * i / totIdx); UpdateProgress(basePct, string.Format(_lang.DownloadingChunk, curIdx, totIdx), cName); byte[] chunkBytes = wc.DownloadData(cUrl); fsOut.Write(chunkBytes, 0, chunkBytes.Length); } } } else { // 單一 payload.zip 回退模式 string fullZipUrl = serverUrl + "payload.zip"; wc.DownloadProgressChanged += (s, e) => { int pct = 10 + (int)(e.ProgressPercentage * 0.7); UpdateProgress(pct, _lang.Downloading, string.Format("{0:F1} MB / {1:F1} MB", e.BytesReceived / 1048576.0, e.TotalBytesToReceive / 1048576.0)); }; wc.DownloadFile(new Uri(fullZipUrl), tempZip); } } UpdateProgress(80, _lang.ExtractingDeploying, tempZip); ExtractZipSafe(tempZip, targetDir); } finally { if (File.Exists(tempZip)) { try { File.Delete(tempZip); } catch {} } } } // 3. 複製安裝程式自身作為解除安裝程式 string selfExe = Application.ExecutablePath; if (File.Exists(selfExe)) { try { File.Copy(selfExe, targetSetup, true); } catch {} } // 保存語系與設定 try { string cfgFile = Path.Combine(targetDir, "setup_config.ini"); File.WriteAllText(cfgFile, "lang=" + _lang.Code + "\r\nurl=" + serverUrl + "\r\n", System.Text.Encoding.UTF8); } catch {} // 4. 建立捷徑 if (!File.Exists(targetIcon)) { try { var asm = Assembly.GetExecutingAssembly(); using (Stream s = asm.GetManifestResourceStream("app_icon.ico")) { if (s != null) { using (FileStream fs = new FileStream(targetIcon, FileMode.Create, FileAccess.Write)) { s.CopyTo(fs); } } } } catch {} } UpdateProgress(90, _lang.CreatingShortcuts, _lang.ShortcutsDetail); if (createDesk) { string desk = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); Program.CreateLnk(Path.Combine(desk, appName + ".lnk"), targetExe, targetIcon, targetDir); } if (createStart) { string progs = Environment.GetFolderPath(Environment.SpecialFolder.Programs); Program.CreateLnk(Path.Combine(progs, appName + ".lnk"), targetExe, targetIcon, targetDir); } // 5. 註冊 Windows 11 設定「已安裝的應用程式」 UpdateProgress(96, _lang.Registering, _lang.RegisterDetail); Program.RegisterApp(targetDir, targetIcon, targetSetup, appName); // 6. 完成 UpdateProgress(100, _lang.InstallDone, _lang.InstallDoneDetail); this.BeginInvoke((Action)(() => { pnlProgress.Visible = false; pnlDone.Visible = true; btnAction.Text = _lang.Finish; btnAction.BackColor = Color.FromArgb(22, 163, 74); btnAction.Enabled = true; btnCancel.Visible = false; })); } } public class ModernUninstaller : Form { private readonly InstallLang _lang; private Panel panelHeader; private Panel panelFooter; private Panel panelContent; private PictureBox picIcon; private Label lblTitle; private Label lblSub; private Label lblPrompt; private Label lblDetail; private ProgressBar progressBar; private Button btnAction; private Button btnCancel; public ModernUninstaller(InstallLang lang = null) { _lang = lang ?? I18n.GetSavedOrDetectedLanguage(); this.Text = string.Format(_lang.UninstallWizardTitle, _lang.AppTitle); this.Size = new Size(500, 310); this.StartPosition = FormStartPosition.CenterScreen; this.FormBorderStyle = FormBorderStyle.FixedSingle; this.MaximizeBox = false; this.BackColor = Color.FromArgb(249, 250, 252); this.Font = new Font("Microsoft JhengHei UI", 9F); this.TopMost = true; if (_lang.IsRtl) { this.RightToLeft = RightToLeft.Yes; this.RightToLeftLayout = true; } // Header panelHeader = new Panel { Dock = DockStyle.Top, Height = 75, BackColor = Color.White }; panelHeader.Paint += (s, e) => { using (Pen p = new Pen(Color.FromArgb(229, 231, 235), 1)) { e.Graphics.DrawLine(p, 0, 74, panelHeader.Width, 74); } }; picIcon = new PictureBox { Size = new Size(44, 44), Location = new Point(24, 16), SizeMode = PictureBoxSizeMode.Zoom }; try { var asm = Assembly.GetExecutingAssembly(); using (Stream sLogo = asm.GetManifestResourceStream("app_logo.png")) { if (sLogo != null) picIcon.Image = Image.FromStream(sLogo); } using (Stream sIco = asm.GetManifestResourceStream("app_icon.ico")) { if (sIco != null) this.Icon = new Icon(sIco); } } catch {} lblTitle = new Label { Text = string.Format(_lang.UninstallHeaderTitle, _lang.AppTitle), Font = new Font("Microsoft JhengHei UI", 12.5F, FontStyle.Bold), ForeColor = Color.FromArgb(24, 24, 27), Location = new Point(80, 16), AutoSize = true }; lblSub = new Label { Text = _lang.UninstallHeaderSub, ForeColor = Color.FromArgb(113, 113, 122), Location = new Point(81, 42), AutoSize = true }; panelHeader.Controls.Add(picIcon); panelHeader.Controls.Add(lblTitle); panelHeader.Controls.Add(lblSub); // Footer panelFooter = new Panel { Dock = DockStyle.Bottom, Height = 60, BackColor = Color.FromArgb(244, 245, 247) }; panelFooter.Paint += (s, e) => { using (Pen p = new Pen(Color.FromArgb(229, 231, 235), 1)) { e.Graphics.DrawLine(p, 0, 0, panelFooter.Width, 0); } }; btnCancel = new Button { Text = _lang.Cancel, Size = new Size(80, 32), Location = new Point(390, 14), FlatStyle = FlatStyle.Flat, BackColor = Color.White, ForeColor = Color.FromArgb(55, 65, 81), Cursor = Cursors.Hand }; btnCancel.FlatAppearance.BorderColor = Color.FromArgb(209, 213, 219); btnCancel.Click += (s, e) => this.Close(); btnAction = new Button { Text = _lang.UninstallAction, Size = new Size(115, 32), Location = new Point(265, 14), FlatStyle = FlatStyle.Flat, BackColor = Color.FromArgb(220, 38, 38), ForeColor = Color.White, Cursor = Cursors.Hand, Font = new Font("Microsoft JhengHei UI", 9F, FontStyle.Bold) }; btnAction.FlatAppearance.BorderSize = 0; btnAction.Click += BtnAction_Click; panelFooter.Controls.Add(btnCancel); panelFooter.Controls.Add(btnAction); // Content panelContent = new Panel { Dock = DockStyle.Fill, BackColor = Color.FromArgb(249, 250, 252) }; lblPrompt = new Label { Text = string.Format(_lang.UninstallPrompt, _lang.AppTitle), Font = new Font("Microsoft JhengHei UI", 10F, FontStyle.Bold), ForeColor = Color.FromArgb(24, 24, 27), Location = new Point(28, 26), AutoSize = true }; lblDetail = new Label { Text = _lang.UninstallDetail, Font = new Font("Microsoft JhengHei UI", 9F), ForeColor = Color.FromArgb(100, 116, 139), Location = new Point(30, 56), Size = new Size(430, 45) }; progressBar = new ProgressBar { Location = new Point(30, 105), Size = new Size(430, 16), Style = ProgressBarStyle.Marquee, Visible = false }; panelContent.Controls.Add(lblPrompt); panelContent.Controls.Add(lblDetail); panelContent.Controls.Add(progressBar); this.Controls.Add(panelContent); this.Controls.Add(panelHeader); this.Controls.Add(panelFooter); } private void BtnAction_Click(object sender, EventArgs e) { if (btnAction.Text == _lang.Finish) { this.Close(); return; } btnAction.Enabled = false; btnCancel.Enabled = false; progressBar.Visible = true; lblPrompt.Text = _lang.UninstallProgressTitle; lblDetail.Text = _lang.UninstallProgressDetail; Timer t = new Timer { Interval = 600 }; t.Tick += (ts, te) => { t.Stop(); try { foreach (Process p in Process.GetProcessesByName("YouTube_Downloader")) { try { p.Kill(); p.WaitForExit(1500); } catch {} } // 清理所有支援語系的可能桌面與開始功能表捷徑 string desk = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); string progs = Environment.GetFolderPath(Environment.SpecialFolder.Programs); foreach (var l in I18n.All) { string dLnk = Path.Combine(desk, l.AppTitle + ".lnk"); if (File.Exists(dLnk)) { try { File.Delete(dLnk); } catch {} } string sLnk = Path.Combine(progs, l.AppTitle + ".lnk"); if (File.Exists(sLnk)) { try { File.Delete(sLnk); } catch {} } } Program.RegDel(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\YouTubeDownloader"); Program.RegDel(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{D3F982A1-5E4C-4B71-9C3E-812C45F20E77}_is1"); string curDir = AppDomain.CurrentDomain.BaseDirectory.TrimEnd('\\', '/'); string targetDirToDelete = null; if (curDir.IndexOf("YouTube Downloader", StringComparison.OrdinalIgnoreCase) >= 0) { targetDirToDelete = curDir; } else { string defDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Programs", "YouTube Downloader"); if (Directory.Exists(defDir)) targetDirToDelete = defDir; } if (!string.IsNullOrEmpty(targetDirToDelete)) { ProcessStartInfo psi = new ProcessStartInfo("cmd.exe", "/c timeout /t 1 /nobreak >nul & rd /s /q \"" + targetDirToDelete + "\""); psi.WindowStyle = ProcessWindowStyle.Hidden; psi.CreateNoWindow = true; Process.Start(psi); } } catch {} progressBar.Visible = false; lblPrompt.Text = _lang.UninstallDoneTitle; lblDetail.Text = string.Format(_lang.UninstallDoneDetail, _lang.AppTitle); btnAction.Text = _lang.Finish; btnAction.BackColor = Color.FromArgb(22, 163, 74); btnAction.Enabled = true; btnCancel.Visible = false; }; t.Start(); } }