Files
MrMahoandClaude Opus 5.5 5fd162fecb Setup-Assistent und Update-Abfrage bei jedem Start
Die heruntergeladene Datei ist ein Setup: Sprache, Ordner (nur ohne Admin-Rechte beschreibbar),
Verknuepfungen, Fortschritt, Fertig-Seite; Ordner steht in der Registry. Deinstallieren loescht im
gewaehlten Ordner nur die eigenen Dateien. Beim Start fragt der Launcher nach neuen Updates
(Launcher und Mod).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:01:27 +01:00

308 lines
14 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
namespace BSO.Launcher;
/// <summary>
/// Setup-Assistent der heruntergeladenen BSO-Launcher-Setup.exe: Willkommen (mit Sprachwahl) -> Ordner und Verknuepfungen
/// -> Installieren -> Fertig. Ist dieselbe oder eine neuere Version schon installiert, bietet er Starten an (gleiche Version:
/// auch Neu installieren). Eigenes dunkles Fenster im Stil des Launchers.
/// </summary>
public sealed class SetupForm : Form
{
static readonly Color Back = Color.FromArgb(0x10, 0x13, 0x1a), Field = Color.FromArgb(0x1a, 0x1f, 0x29), Line = Color.FromArgb(0x26, 0x2c, 0x38),
Bright = Color.FromArgb(0xe8, 0xec, 0xf2), Muted = Color.FromArgb(0x8b, 0x95, 0xa5), Accent = Color.FromArgb(0x1f, 0x6f, 0xe5),
Bad = Color.FromArgb(0xff, 0x8a, 0x8d);
const int Header = 96;
readonly string version = SelfUpdate.CurrentVersion;
readonly string? installed = Installer.InstalledVersion();
readonly FlowLayoutPanel content = new(), buttons = new(), langs = new();
readonly Label close = new();
readonly Image? logo = Installer.Logo();
Action page;
bool installing;
// Auswahl - bleibt beim Sprachwechsel und Zurueck erhalten
string folder = Installer.RegisteredDir ?? Installer.DefaultDir;
bool desktop, startMenu;
string? installedTo;
static string L(string de, string en) => Lang.Current == "en" ? en : de;
public SetupForm()
{
bool fresh = installed == null;
desktop = fresh || Installer.HasDesktopShortcut;
startMenu = fresh || Installer.HasStartMenuShortcut;
Text = "BSO Launcher Setup";
FormBorderStyle = FormBorderStyle.None;
StartPosition = FormStartPosition.CenterScreen;
AutoScaleMode = AutoScaleMode.Dpi;
ClientSize = new Size(600, 430);
BackColor = Back;
ForeColor = Bright;
Font = new Font("Segoe UI", 10f);
DoubleBuffered = true;
try { Icon = Icon.ExtractAssociatedIcon(Environment.ProcessPath ?? Application.ExecutablePath); } catch { }
content.SetBounds(32, Header + 18, 536, 250);
content.FlowDirection = FlowDirection.TopDown;
content.WrapContents = false;
buttons.SetBounds(32, 368, 536, 44);
buttons.FlowDirection = FlowDirection.RightToLeft;
buttons.WrapContents = false;
langs.SetBounds(460, 34, 96, 30);
langs.FlowDirection = FlowDirection.LeftToRight;
langs.WrapContents = false;
close.SetBounds(560, 8, 32, 28);
close.Text = "✕";
close.ForeColor = Muted;
close.TextAlign = ContentAlignment.MiddleCenter;
close.Cursor = Cursors.Hand;
close.Click += (_, _) => { if (!installing) Close(); };
close.MouseEnter += (_, _) => close.ForeColor = Bright;
close.MouseLeave += (_, _) => close.ForeColor = Muted;
Controls.AddRange(new Control[] { content, buttons, langs, close });
MouseDown += (_, e) => { if (e.Button == MouseButtons.Left && e.Y < Header) DragWindow(); };
page = Welcome;
Go(Welcome);
}
// ---------------------------------------------------------------- Seiten
void Welcome()
{
if (installed != null && !SelfUpdate.IsNewer(version, installed))
{
bool same = installed == version;
Title(L("BSO Launcher ist schon installiert", "BSO Launcher is already installed"));
Paragraph(same
? L($"Version {installed} ist auf diesem PC schon installiert. Du kannst ihn starten oder neu installieren.",
$"Version {installed} is already installed on this PC. You can start it or install it again.")
: L($"Auf diesem PC ist schon die neuere Version {installed} installiert (dieses Setup: {version}).",
$"The newer version {installed} is already installed on this PC (this setup: {version})."));
Paragraph(Installer.InstallDir, Muted, 9f);
Button(L("Launcher starten", "Start launcher"), true, () => { Installer.StartInstalled(); Close(); });
if (same) Button(L("Neu installieren", "Reinstall"), false, () => Go(Options));
return;
}
if (installed != null)
{
Title(L($"Update auf Version {version}", $"Update to version {version}"));
Paragraph(L($"Installiert ist Version {installed}. Das Setup aktualisiert den BSO Launcher – deine Anmeldung und Einstellungen bleiben erhalten.",
$"Version {installed} is installed. This setup updates the BSO Launcher – your sign-in and settings are kept."));
}
else
{
Title(L("Willkommen", "Welcome"));
Paragraph(L($"Dieses Setup installiert den BSO Launcher {version} – Multiplayer für Bus-Simulator 2012.",
$"This setup installs the BSO Launcher {version} – multiplayer for Bus-Simulator 2012."));
Paragraph(L("Der Launcher installiert danach die BSO-Mod für dich und hält sich und die Mod selbst aktuell. Admin-Rechte brauchst du nicht.",
"The launcher then installs the BSO mod for you and keeps itself and the mod up to date. No admin rights needed."));
}
Paragraph(L("Sprache wählst du oben rechts.", "Choose your language in the top right."), Muted, 9f);
Button(L("Weiter", "Next"), true, () => Go(Options));
Button(L("Abbrechen", "Cancel"), false, Close);
}
void Options()
{
Title(L("Ordner und Verknüpfungen", "Folder and shortcuts"));
Paragraph(L("Installieren in:", "Install to:"), Muted, 9f, 0);
var row = new FlowLayoutPanel { AutoSize = true, WrapContents = false, Margin = new Padding(0, 4, 0, 4) };
var path = new TextBox { Text = folder, Width = 404, BackColor = Field, ForeColor = Bright, BorderStyle = BorderStyle.FixedSingle, Margin = new Padding(0, 2, 8, 0) };
path.TextChanged += (_, _) => folder = path.Text;
var browse = MakeButton(L("Ändern …", "Change …"), false, () =>
{
using var dialog = new FolderBrowserDialog { Description = L("Ordner für den BSO Launcher wählen", "Choose a folder for the BSO Launcher"), UseDescriptionForTitle = true };
try { dialog.InitialDirectory = Directory.Exists(folder) ? folder : Path.GetDirectoryName(folder) ?? ""; } catch { }
if (dialog.ShowDialog(this) == DialogResult.OK) path.Text = Installer.NormalizeDir(dialog.SelectedPath);
});
browse.MinimumSize = new Size(110, 30);
row.Controls.AddRange(new Control[] { path, browse });
content.Controls.Add(row);
Paragraph(L($"Braucht etwa {Installer.SelfSize / 1024 / 1024} MB · nur für deinen Windows-Benutzer",
$"Needs about {Installer.SelfSize / 1024 / 1024} MB · only for your Windows user"), Muted, 9f);
Check(L("Verknüpfung auf dem Desktop", "Desktop shortcut"), desktop, v => desktop = v);
Check(L("Eintrag im Startmenü", "Start menu entry"), startMenu, v => startMenu = v);
var error = Paragraph("", Bad, 9.5f);
Button(L("Installieren", "Install"), true, () =>
{
string dir;
try { dir = Installer.NormalizeDir(folder); }
catch { error.Text = L("Ungültiger Ordner", "Invalid folder"); return; }
if (Installer.CheckWritable(dir) is { } problem) { error.Text = Lang.T(problem); return; }
Go(() => Install(dir));
});
Button(L("Zurück", "Back"), false, () => Go(Welcome));
}
void Install(string dir)
{
Title(L("Wird installiert …", "Installing …"));
var status = Paragraph(Lang.T("Vorbereiten …"), Muted, 9.5f);
var bar = new Bar { Width = 536, Height = 8, Margin = new Padding(0, 8, 0, 0) };
content.Controls.Add(bar);
installing = true;
langs.Enabled = false;
_ = Task.Run(() =>
{
try
{
Installer.Install(dir, desktop, startMenu, (p, text) => BeginInvoke(() => { bar.Value = p; status.Text = text; }));
installedTo = dir;
BeginInvoke(() => { installing = false; langs.Enabled = true; Go(Finish); });
}
catch (Exception e)
{
BeginInvoke(() => { installing = false; langs.Enabled = true; Go(() => Failed(e.Message)); });
}
});
}
void Failed(string message)
{
Title(L("Installation fehlgeschlagen", "Installation failed"));
Paragraph(message, Bad);
Paragraph(L("Läuft der Launcher noch? Dann schliessen und nochmal versuchen.", "Is the launcher still running? Close it and try again."), Muted, 9f);
Button(L("Schliessen", "Close"), false, Close);
Button(L("Zurück", "Back"), true, () => Go(Options));
}
void Finish()
{
Title(L("Fertig!", "Done!"));
Paragraph(L($"Der BSO Launcher {version} ist installiert.", $"The BSO Launcher {version} is installed."));
Paragraph(installedTo ?? Installer.InstallDir, Muted, 9f);
Paragraph(L("Deinstallieren kannst du ihn jederzeit in den Launcher-Einstellungen oder unter Windows-Einstellungen → Apps.",
"You can uninstall it any time in the launcher settings or under Windows Settings → Apps."), Muted, 9f);
bool start = true;
Check(L("BSO Launcher jetzt starten", "Start BSO Launcher now"), true, v => start = v);
Button(L("Fertig", "Finish"), true, () => { if (start) Installer.StartInstalled(); Close(); });
}
// ---------------------------------------------------------------- Bausteine
void Go(Action next)
{
page = next;
Invalidate(); // Kopf (Sprache)
SuspendLayout();
content.Controls.Clear();
buttons.Controls.Clear();
DrawLangs();
next();
ResumeLayout();
}
void DrawLangs()
{
langs.Controls.Clear();
foreach (string l in new[] { "de", "en" })
{
var b = MakeButton(l.ToUpperInvariant(), Lang.Current == l, () =>
{
Lang.Set(l);
try { var s = Settings.Load(); s.Language = l; s.Save(); } catch { } // gleich auch fuer den Launcher
Go(page);
});
b.AutoSize = false;
b.MinimumSize = Size.Empty;
b.Size = new Size(44, 28);
b.Padding = Padding.Empty;
b.Margin = new Padding(0, 0, 4, 0);
b.Font = new Font("Segoe UI", 8.5f, FontStyle.Bold);
langs.Controls.Add(b);
}
}
void Title(string text) => Paragraph(text, Bright, 15f, 6, FontStyle.Bold);
Label Paragraph(string text, Color? color = null, float size = 10f, int below = 8, FontStyle style = FontStyle.Regular)
{
var label = new Label
{
Text = text, AutoSize = true, MaximumSize = new Size(536, 0), ForeColor = color ?? Bright,
Font = new Font("Segoe UI", size, style), Margin = new Padding(0, 0, 0, below), UseMnemonic = false
};
content.Controls.Add(label);
return label;
}
void Check(string text, bool value, Action<bool> changed)
{
var box = new CheckBox { Text = text, Checked = value, AutoSize = true, ForeColor = Bright, Margin = new Padding(0, 4, 0, 2), Cursor = Cursors.Hand };
box.CheckedChanged += (_, _) => changed(box.Checked);
content.Controls.Add(box);
}
void Button(string text, bool primary, Action click) => buttons.Controls.Add(MakeButton(text, primary, click));
static Button MakeButton(string text, bool primary, Action click)
{
var b = new Button
{
Text = text, AutoSize = true, MinimumSize = new Size(120, 36), FlatStyle = FlatStyle.Flat, Cursor = Cursors.Hand,
BackColor = primary ? Accent : Field, ForeColor = Color.White, Font = new Font("Segoe UI", 9.5f, FontStyle.Bold),
Margin = new Padding(10, 0, 0, 0), Padding = new Padding(12, 0, 12, 0), UseMnemonic = false
};
b.FlatAppearance.BorderSize = primary ? 0 : 1;
b.FlatAppearance.BorderColor = Line;
b.Click += (_, _) => click();
return b;
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
using (var border = new Pen(Line)) { g.DrawRectangle(border, 0, 0, ClientSize.Width - 1, ClientSize.Height - 1); g.DrawLine(border, 0, Header, ClientSize.Width, Header); }
if (logo != null) g.DrawImage(logo, 28, 22, 52, 52);
using var title = new Font("Segoe UI", 15f, FontStyle.Bold);
TextRenderer.DrawText(g, "BSO Launcher", title, new Point(92, 20), Bright);
using var sub = new Font("Segoe UI", 9f);
TextRenderer.DrawText(g, L($"Setup · Version {version}", $"Setup · version {version}"), sub, new Point(94, 52), Muted);
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
if (installing && e.CloseReason == CloseReason.UserClosing) e.Cancel = true; // nicht mitten im Kopieren abbrechen
base.OnFormClosing(e);
}
[DllImport("user32.dll")] static extern bool ReleaseCapture();
[DllImport("user32.dll")] static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
void DragWindow()
{
ReleaseCapture();
SendMessage(Handle, 0x00A1, 2, IntPtr.Zero); // WM_NCLBUTTONDOWN, HTCAPTION
}
/// <summary>Fortschrittsbalken im Launcher-Stil (Verlauf blau -> tuerkis).</summary>
sealed class Bar : Control
{
double value;
[System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)]
public double Value { get => value; set { this.value = Math.Clamp(value, 0, 1); Invalidate(); } }
public Bar() { DoubleBuffered = true; }
protected override void OnPaint(PaintEventArgs e)
{
var all = new Rectangle(0, 0, Width, Height);
using (var back = new SolidBrush(Color.FromArgb(0x23, 0x2a, 0x36))) e.Graphics.FillRectangle(back, all);
if (value <= 0) return;
using var fill = new LinearGradientBrush(all, Color.FromArgb(0x1f, 0x6f, 0xe5), Color.FromArgb(0x5e, 0xe4, 0xff), 0f);
e.Graphics.FillRectangle(fill, new Rectangle(0, 0, (int)(Width * value), Height));
}
}
}