Skip to content

Commit 90178a8

Browse files
committed
language translations
1 parent 598f402 commit 90178a8

File tree

3 files changed

+299
-21
lines changed

3 files changed

+299
-21
lines changed

README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,67 @@ Download the latest standalone executable from the [Releases](https://github.com
6464
- **Linux**: `SwitchCraft-linux`
6565
- **macOS**: `SwitchCraft-macos`
6666

67+
> [!WARNING]
68+
> ### ⚠️ False Positive Virus Warnings
69+
>
70+
> SwitchCraft may trigger security warnings from browsers, operating systems, or antivirus software. **This is a false positive** – the application is completely safe.
71+
72+
#### Why this happens
73+
74+
<details>
75+
<summary><b>🌐 Google Chrome / Edge</b> – "This file may be dangerous"</summary>
76+
77+
Chrome uses **Safe Browsing** which flags files based on:
78+
- **Download frequency**: New/rarely downloaded files are flagged regardless of content
79+
- **Unsigned executables**: SwitchCraft doesn't have an expensive code-signing certificate
80+
- **Executable type**: All `.exe` downloads trigger extra scrutiny
81+
82+
**Solution**: Click the `^` arrow → "Keep dangerous file" → "Keep anyway"
83+
</details>
84+
85+
<details>
86+
<summary><b>🪟 Windows SmartScreen</b> – "Windows protected your PC"</summary>
87+
88+
Microsoft SmartScreen blocks apps with low "reputation score" based on:
89+
- **Unknown publisher**: No code-signing certificate = "Unknown Publisher"
90+
- **Low download count**: New apps have no established reputation
91+
- **Application behavior**: SwitchCraft reads PE headers and runs processes (for help text detection), which looks suspicious to heuristics
92+
93+
**Solution**: Click "More info" → "Run anyway"
94+
95+
> After many users click "Run anyway", SmartScreen learns the app is safe.
96+
</details>
97+
98+
<details>
99+
<summary><b>🛡️ Windows Defender / Microsoft Defender</b> – "Trojan:Win32/Wacatac.B!ml"</summary>
100+
101+
Defender's **machine learning models** flag SwitchCraft because:
102+
- **PyInstaller-packaged apps** are commonly abused by malware
103+
- **Process execution**: Running `installer.exe /?` to detect help text triggers behavioral detection
104+
- **PE file parsing**: Reading executable metadata is similar to what malware does
105+
- **Archive extraction**: Using 7-Zip to extract nested files resembles unpacking behavior
106+
107+
**Solution**: Add an exclusion in Windows Security → Virus & threat protection → Exclusions
108+
</details>
109+
110+
<details>
111+
<summary><b>🔒 Other Antivirus (Kaspersky, Avast, Norton, etc.)</b></summary>
112+
113+
Third-party AV software may flag SwitchCraft because:
114+
- **Heuristic detection**: The combination of PE parsing + process spawning + file extraction triggers generic "suspicious behavior" rules
115+
- **VirusTotal reputation**: Some AV vendors share detections, causing a cascade of false positives
116+
- **UPX compression**: PyInstaller uses UPX to compress executables, which is also used by malware to hide code
117+
118+
**Solution**: Add SwitchCraft to your antivirus exclusion/whitelist
119+
</details>
120+
121+
#### How to verify the file is safe
122+
123+
1. **Check the source**: Download only from [GitHub Releases](https://github.com/FaserF/SwitchCraft/releases)
124+
2. **Verify the hash**: Compare SHA256 hash with the one published on the release page
125+
3. **Scan on VirusTotal**: Upload to [virustotal.com](https://www.virustotal.com) – expect 2-5 detections on new releases (these decrease over time)
126+
4. **Build from source**: Clone this repo and build yourself with `pyinstaller switchcraft.spec`
127+
67128
No Python installation required!
68129

69130
### From Source

src/switchcraft/gui/app.py

Lines changed: 119 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from PIL import Image
66
import webbrowser
77
import logging
8+
from tkinter import messagebox
89
import os
910
import sys
1011

@@ -101,43 +102,48 @@ def check_updates_silently(self):
101102
threading.Thread(target=self._run_update_check, daemon=True).start()
102103

103104
def _run_update_check(self, show_no_update=False):
104-
checker = UpdateChecker()
105-
has_update, version, data = checker.check_for_updates()
105+
try:
106+
checker = UpdateChecker()
107+
has_update, version, data = checker.check_for_updates()
106108

107-
if has_update:
108-
self.after(0, lambda: self.show_update_dialog(checker))
109-
elif show_no_update:
110-
self.after(0, lambda: ctk.filedialog.showinfo("Check for Updates", f"You are up to date! ({__version__})"))
109+
if has_update:
110+
self.after(0, lambda: self.show_update_dialog(checker))
111+
elif show_no_update:
112+
self.after(0, lambda: messagebox.showinfo("Check for Updates", f"You are up to date! ({__version__})"))
113+
except Exception as e:
114+
logger.error(f"Update check failed: {e}")
115+
if show_no_update:
116+
self.after(0, lambda err=str(e): messagebox.showerror(i18n.get("update_check_failed"), f"{i18n.get('could_not_check')}\n{err}"))
111117

112118
def show_update_dialog(self, checker):
113119
dialog = ctk.CTkToplevel(self)
114120
dialog.title("Update Available 🚀")
115121
dialog.geometry("500x400")
116122
dialog.transient(self)
117123

118-
ctk.CTkLabel(dialog, text="A new version of SwitchCraft is available!", font=ctk.CTkFont(size=16, weight="bold")).pack(pady=10)
124+
ctk.CTkLabel(dialog, text=i18n.get("update_available"), font=ctk.CTkFont(size=16, weight="bold")).pack(pady=10)
119125

120126
info_frame = ctk.CTkFrame(dialog)
121127
info_frame.pack(fill="both", expand=True, padx=20, pady=10)
122128

123-
ctk.CTkLabel(info_frame, text=f"Current Version: {checker.current_version}").pack(anchor="w", padx=10, pady=2)
124-
ctk.CTkLabel(info_frame, text=f"New Version: {checker.latest_version}", text_color="green").pack(anchor="w", padx=10, pady=2)
129+
ctk.CTkLabel(info_frame, text=f"{i18n.get('current_version')}: {checker.current_version}").pack(anchor="w", padx=10, pady=2)
130+
ctk.CTkLabel(info_frame, text=f"{i18n.get('new_version')}: {checker.latest_version}", text_color="green").pack(anchor="w", padx=10, pady=2)
125131

126132
date_str = checker.release_date.split("T")[0] if checker.release_date else "Unknown"
127-
ctk.CTkLabel(info_frame, text=f"Released: {date_str}").pack(anchor="w", padx=10, pady=2)
133+
ctk.CTkLabel(info_frame, text=f"{i18n.get('released')}: {date_str}").pack(anchor="w", padx=10, pady=2)
128134

129-
ctk.CTkLabel(dialog, text="Changelog:", font=ctk.CTkFont(weight="bold")).pack(anchor="w", padx=20)
135+
ctk.CTkLabel(dialog, text=f"{i18n.get('changelog')}:", font=ctk.CTkFont(weight="bold")).pack(anchor="w", padx=20)
130136
textbox = ctk.CTkTextbox(dialog, height=100)
131137
textbox.pack(fill="x", padx=20, pady=5)
132-
textbox.insert("0.0", checker.release_notes or "No changelog provided.")
138+
textbox.insert("0.0", checker.release_notes or i18n.get("no_changelog"))
133139
textbox.configure(state="disabled")
134140

135141
btn_frame = ctk.CTkFrame(dialog, fg_color="transparent")
136142
btn_frame.pack(fill="x", padx=20, pady=20)
137143

138144
download_url = checker.get_download_url()
139-
ctk.CTkButton(btn_frame, text="Download Update", command=lambda: webbrowser.open(download_url)).pack(side="left", padx=5)
140-
ctk.CTkButton(btn_frame, text="Skip", fg_color="gray", command=dialog.destroy).pack(side="right", padx=5)
145+
ctk.CTkButton(btn_frame, text=i18n.get("download_update"), command=lambda: webbrowser.open(download_url)).pack(side="left", padx=5)
146+
ctk.CTkButton(btn_frame, text=i18n.get("skip"), fg_color="gray", command=dialog.destroy).pack(side="right", padx=5)
141147

142148

143149
# --- Analyzer Tab ---
@@ -337,7 +343,7 @@ def show_results(self, info, winget_url, brute_force_data=None, nested_data=None
337343
# Brute Force Output Log
338344
if brute_force_data:
339345
self.add_separator()
340-
lbl = ctk.CTkLabel(self.result_frame, text="Automated Analysis Output", font=ctk.CTkFont(weight="bold"))
346+
lbl = ctk.CTkLabel(self.result_frame, text=i18n.get("automated_output"), font=ctk.CTkFont(weight="bold"))
341347
lbl.pack(pady=5)
342348

343349
log_box = ctk.CTkTextbox(self.result_frame, height=150, fg_color="black", text_color="#00FF00", font=("Consolas", 11))
@@ -346,7 +352,7 @@ def show_results(self, info, winget_url, brute_force_data=None, nested_data=None
346352
log_box.pack(fill="x", pady=5)
347353

348354
if "MSI Wrapper" in info.installer_type:
349-
ctk.CTkLabel(self.result_frame, text="💡 Detected MSI Wrapper! Standard MSI switches may work.", text_color="cyan", font=ctk.CTkFont(weight="bold")).pack(pady=5)
355+
ctk.CTkLabel(self.result_frame, text=i18n.get("msi_wrapper_tip"), text_color="cyan", font=ctk.CTkFont(weight="bold")).pack(pady=5)
350356

351357
# Winget
352358
self.add_separator()
@@ -361,9 +367,103 @@ def show_results(self, info, winget_url, brute_force_data=None, nested_data=None
361367
command=lambda: webbrowser.open(winget_url))
362368
link_btn.pack(pady=5, padx=10, fill="x")
363369
else:
364-
w_lbl = ctk.CTkLabel(winget_panel, text="Winget: No match found / No properties detected", text_color="gray")
370+
w_lbl = ctk.CTkLabel(winget_panel, text=i18n.get("winget_no_match"), text_color="gray")
365371
w_lbl.pack(pady=5)
366372

373+
# Parameter List - Show all found parameters with explanations
374+
all_params = []
375+
if info.install_switches:
376+
all_params.extend(info.install_switches)
377+
if info.uninstall_switches:
378+
all_params.extend(info.uninstall_switches)
379+
380+
if all_params:
381+
self.show_all_parameters(all_params)
382+
383+
def show_all_parameters(self, params):
384+
"""Display all found parameters with explanations."""
385+
self.add_separator()
386+
387+
# Header
388+
header_frame = ctk.CTkFrame(self.result_frame, fg_color=("#E8F5E9", "#1B5E20"), corner_radius=8)
389+
header_frame.pack(fill="x", pady=10, padx=5)
390+
391+
ctk.CTkLabel(
392+
header_frame,
393+
text=f"📋 {i18n.get('found_params')}",
394+
font=ctk.CTkFont(size=14, weight="bold"),
395+
text_color=("black", "white")
396+
).pack(pady=8)
397+
398+
# Separate known and unknown params
399+
known_params = []
400+
unknown_params = []
401+
402+
for param in params:
403+
explanation = i18n.get_param_explanation(param)
404+
if explanation:
405+
known_params.append((param, explanation))
406+
else:
407+
unknown_params.append(param)
408+
409+
# Known parameters with explanations
410+
if known_params:
411+
known_frame = ctk.CTkFrame(self.result_frame, fg_color=("gray90", "gray25"), corner_radius=5)
412+
known_frame.pack(fill="x", pady=5, padx=10)
413+
414+
ctk.CTkLabel(
415+
known_frame,
416+
text=f"✓ {i18n.get('known_params')}",
417+
font=ctk.CTkFont(size=12, weight="bold"),
418+
text_color="green"
419+
).pack(anchor="w", padx=10, pady=5)
420+
421+
for param, explanation in known_params:
422+
param_row = ctk.CTkFrame(known_frame, fg_color="transparent")
423+
param_row.pack(fill="x", padx=10, pady=2)
424+
425+
# Parameter name (monospace, colored)
426+
ctk.CTkLabel(
427+
param_row,
428+
text=param,
429+
font=("Consolas", 12),
430+
text_color=("#0066CC", "#66B3FF"),
431+
width=180,
432+
anchor="w"
433+
).pack(side="left")
434+
435+
# Explanation
436+
ctk.CTkLabel(
437+
param_row,
438+
text=f"→ {explanation}",
439+
text_color=("gray40", "gray70"),
440+
anchor="w"
441+
).pack(side="left", fill="x", expand=True)
442+
443+
# Padding at bottom of known params
444+
ctk.CTkLabel(known_frame, text="", height=5).pack()
445+
446+
# Unknown parameters
447+
if unknown_params:
448+
unknown_frame = ctk.CTkFrame(self.result_frame, fg_color=("gray95", "gray30"), corner_radius=5)
449+
unknown_frame.pack(fill="x", pady=5, padx=10)
450+
451+
ctk.CTkLabel(
452+
unknown_frame,
453+
text=f"? {i18n.get('unknown_params')}",
454+
font=ctk.CTkFont(size=12, weight="bold"),
455+
text_color="orange"
456+
).pack(anchor="w", padx=10, pady=5)
457+
458+
unknown_text = " ".join(unknown_params)
459+
ctk.CTkLabel(
460+
unknown_frame,
461+
text=unknown_text,
462+
font=("Consolas", 11),
463+
text_color=("gray50", "gray60"),
464+
wraplength=400
465+
).pack(anchor="w", padx=10, pady=(0, 8))
466+
367467
def show_nested_executables(self, nested_data, parent_info):
368468
"""Display nested executables found inside an extracted archive."""
369469

@@ -609,7 +709,7 @@ def setup_settings_tab(self):
609709
# Update Check
610710
frame_upd = ctk.CTkFrame(self.tab_settings)
611711
frame_upd.pack(fill="x", padx=10, pady=10)
612-
ctk.CTkButton(frame_upd, text="Check for Updates", command=lambda: self._run_update_check(show_no_update=True)).pack(pady=10)
712+
ctk.CTkButton(frame_upd, text=i18n.get("check_updates"), command=lambda: self._run_update_check(show_no_update=True)).pack(pady=10)
613713

614714
# About
615715
frame_about = ctk.CTkFrame(self.tab_settings, fg_color="transparent")
@@ -624,7 +724,7 @@ def setup_settings_tab(self):
624724
link.pack(pady=5)
625725

626726
# Footer
627-
ctk.CTkLabel(self.tab_settings, text="Brought to you by FaserF", text_color="gray").pack(side="bottom", pady=10)
727+
ctk.CTkLabel(self.tab_settings, text=i18n.get("brought_by"), text_color="gray").pack(side="bottom", pady=10)
628728

629729
def change_theme(self, value):
630730
if value == i18n.get("settings_light"): ctk.set_appearance_mode("Light")

0 commit comments

Comments
 (0)