Upload via GUI (90 Dateien)

This commit is contained in:
2026-10-02 06:22:54 +00:00
parent d268e548f5
commit 906ac75443
39 changed files with 4183 additions and 0 deletions
+440
View File
@@ -0,0 +1,440 @@
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
namespace AeroMCSetup;
public partial class MainWindow : Window
{
private readonly SetupMode _mode;
private int _page;
private bool _busy;
private bool _done;
private string? _installedExe;
// andere Launcher für den optionalen Import (leer → Import-Schritt entfällt)
private List<FoundLauncher> _found = [];
private bool HasImport => _found.Count > 0;
private const int PageNumImport = 3;
private const int PageNumProgress = 4;
public MainWindow(SetupMode mode)
{
InitializeComponent();
_mode = mode;
VersionText.Text = $"Version {Installer.Version}";
LicenseText.Text = Installer.LicenseText;
var installed = Installer.InstalledDir;
DirBox.Text = installed ?? Installer.DefaultDir;
DesktopLinkBox.IsChecked = installed is null || Installer.DesktopLinkExists;
if (installed is not null)
{
UpdateHint.Visibility = Visibility.Visible;
UpdateHintText.Text = Installer.InstalledVersion == Installer.Version
? $"AeroMC {Installer.Version} ist bereits installiert und wird neu installiert. Deine Instanzen, Welten und Einstellungen bleiben erhalten."
: $"Die vorhandene Installation{(Installer.InstalledVersion is { } v ? $" (Version {v})" : "")} wird durch Version {Installer.Version} ersetzt. Deine Instanzen, Welten und Einstellungen bleiben erhalten.";
}
if (Installer.IsRunning(installed)) RunningHint.Visibility = Visibility.Visible;
switch (mode)
{
case SetupMode.Uninstall:
Title = WindowTitle.Text = "AeroMC deinstallieren";
Steps.Visibility = Visibility.Collapsed;
ShowOnly(PageUninstall);
NextButton.Content = "Deinstallieren";
BackButton.Visibility = Visibility.Collapsed;
LoadDataInfo();
break;
case SetupMode.Update:
// aus dem Launcher heraus: ohne Rückfragen direkt aktualisieren und danach wieder starten
Title = WindowTitle.Text = "AeroMC wird aktualisiert";
Steps.Visibility = Visibility.Collapsed;
Loaded += async (_, _) => await RunInstall(DirBox.Text, DesktopLinkBox.IsChecked == true, update: true);
ShowOnly(PageProgress);
ProgressTitle.Text = "AeroMC wird aktualisiert …";
BackButton.Visibility = Visibility.Hidden;
break;
default:
SetupImportPage();
ShowPage(0);
break;
}
}
// ---------- Seiten ----------
private UIElement? _shownPage;
/// <summary>Seite wechseln – mit kurzem Einblenden von rechts.</summary>
private void ShowOnly(UIElement page)
{
foreach (var p in new UIElement[] { PageWelcome, PageLicense, PageOptions, PageImport, PageProgress, PageUninstall })
p.Visibility = p == page ? Visibility.Visible : Visibility.Collapsed;
if (page == _shownPage) return;
_shownPage = page;
var ease = new CubicEase { EasingMode = EasingMode.EaseOut };
var slide = new TranslateTransform(18, 0);
page.RenderTransform = slide;
slide.BeginAnimation(TranslateTransform.XProperty, new DoubleAnimation(18, 0, TimeSpan.FromMilliseconds(260)) { EasingFunction = ease });
page.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(220)));
}
// Farben der Schritt-Anzeige in der (immer dunkelblauen) Seitenleiste
private static readonly Brush StepDoneFill = Frozen(Color.FromRgb(0x4A, 0x86, 0xEA));
private static readonly Brush StepCurrentFill = Brushes.White;
private static readonly Brush StepCurrentText = Frozen(Color.FromRgb(0x1C, 0x4E, 0xB8));
private static readonly Brush StepUpcoming = Frozen(Color.FromRgb(0x7F, 0x95, 0xC2));
private static readonly Brush StepUpcomingStroke = Frozen(Color.FromRgb(0x46, 0x5E, 0x92));
private static readonly Brush StepDoneText = Frozen(Color.FromRgb(0xB9, 0xC8, 0xE8));
private static readonly Brush StepLineIdle = Frozen(Color.FromRgb(0x2E, 0x45, 0x78));
private static Brush Frozen(Color c)
{
var b = new SolidColorBrush(c);
b.Freeze();
return b;
}
private void UpdateSteps(int page)
{
var dots = new[] { Dot0, Dot1, Dot2, Dot3, Dot4 };
var nums = new[] { Num0, Num1, Num2, Num3, Num4 };
var labels = new[] { Label0, Label1, Label2, Label3, Label4 };
var lines = new[] { Line0, Line1, Line2, Line3 };
var iconFont = (FontFamily)FindResource("IconFont");
var uiFont = (FontFamily)FindResource("UiFont");
// ohne gefundene Launcher fällt der Import-Schritt weg → Nummern rücken auf
ImportStepRow.Visibility = Line3.Visibility = HasImport ? Visibility.Visible : Visibility.Collapsed;
int number = 0;
for (int i = 0; i < dots.Length; i++)
{
if (i == PageNumImport && !HasImport) continue;
number++;
bool done = i < page || (_done && i == page);
bool current = i == page && !done;
dots[i].Background = done ? StepDoneFill : current ? StepCurrentFill : Brushes.Transparent;
dots[i].BorderBrush = done ? StepDoneFill : current ? StepCurrentFill : StepUpcomingStroke;
nums[i].Text = done ? "" : number.ToString();
nums[i].FontFamily = done ? iconFont : uiFont;
nums[i].FontSize = done ? 11 : 12;
nums[i].Foreground = done ? Brushes.White : current ? StepCurrentText : StepUpcoming;
labels[i].Foreground = current ? Brushes.White : done ? StepDoneText : StepUpcoming;
labels[i].FontWeight = current ? FontWeights.SemiBold : FontWeights.Normal;
}
for (int i = 0; i < lines.Length; i++)
lines[i].Background = i < page ? StepDoneFill : StepLineIdle;
}
private void ShowPage(int page)
{
_page = page;
ShowOnly(page switch { 0 => PageWelcome, 1 => PageLicense, 2 => PageOptions, PageNumImport => PageImport, _ => PageProgress });
UpdateSteps(page);
BackButton.Visibility = page is 1 or 2 or PageNumImport ? Visibility.Visible : Visibility.Hidden;
NextButton.Content = page switch
{
2 => HasImport ? "Weiter" : "Installieren",
PageNumImport => "Installieren",
PageNumProgress => "Fertigstellen",
_ => "Weiter",
};
NextButton.IsEnabled = page != 1 || AcceptLicense.IsChecked == true;
}
private void AcceptLicense_Click(object sender, RoutedEventArgs e) => NextButton.IsEnabled = AcceptLicense.IsChecked == true;
private void Back_Click(object sender, RoutedEventArgs e)
{
if (!_busy && _page > 0) ShowPage(_page - 1);
}
private async void Next_Click(object sender, RoutedEventArgs e)
{
if (_busy) return;
if (_mode == SetupMode.Uninstall)
{
if (_done) { Close(); return; }
await RunUninstall();
return;
}
if (_mode == SetupMode.Update)
{
Close(); // nur nach einem Fehler erreichbar
return;
}
switch (_page)
{
case 0: ShowPage(1); break;
case 1 when AcceptLicense.IsChecked == true: ShowPage(2); break;
case 2:
var dir = DirBox.Text.Trim();
if (!IsFullPath(dir) || dir.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
{
MessageBox.Show(this, "Bitte einen gültigen Ordner angeben, z. B. C:\\Spiele\\AeroMC.", "AeroMC Setup");
return;
}
if (dir.TrimEnd('\\', '/').Length > Installer.MaxDirLength)
{
MessageBox.Show(this, $"Der Ordnerpfad ist zu lang ({dir.TrimEnd('\\', '/').Length} Zeichen). Windows erlaubt hier höchstens " +
$"{Installer.MaxDirLength} Zeichen – bitte einen kürzeren Ordner wählen, z. B. C:\\Spiele\\AeroMC.", "AeroMC Setup");
return;
}
if (HasImport) ShowPage(PageNumImport);
else await RunInstall(dir, DesktopLinkBox.IsChecked == true, update: false);
break;
case PageNumImport:
await RunInstall(DirBox.Text.Trim(), DesktopLinkBox.IsChecked == true, update: false);
break;
case PageNumProgress:
if (LaunchBox.IsChecked == true) StartApp();
Close();
break;
}
}
// ---------- Import aus anderen Launchern ----------
private void SetupImportPage()
{
try { _found = LauncherImport.Detect(); } catch { _found = []; }
ImportList.Children.Clear();
for (int i = 0; i < _found.Count; i++)
{
var f = _found[i];
var text = new StackPanel { Margin = new Thickness(4, 0, 0, 0) };
text.Children.Add(new TextBlock { Text = f.Name, FontWeight = FontWeights.SemiBold });
var detail = new TextBlock
{
Text = $"{f.Count} {f.Unit} gefunden · {f.Path}",
FontSize = 12,
Margin = new Thickness(0, 2, 0, 0),
TextTrimming = TextTrimming.CharacterEllipsis,
ToolTip = f.Path,
};
detail.SetResourceReference(TextBlock.ForegroundProperty, "TextSecondaryBrush");
text.Children.Add(detail);
var box = new CheckBox { Content = text, Tag = f, Margin = new Thickness(0, 10, 0, 10), IsChecked = false };
if (i > 0)
{
var line = new Border { Height = 1 };
line.SetResourceReference(Border.BackgroundProperty, "ControlStrokeBrush");
ImportList.Children.Add(line);
}
ImportList.Children.Add(box);
}
}
private List<FoundLauncher> SelectedImports() =>
ImportList.Children.OfType<CheckBox>()
.Where(c => c.IsChecked == true && c.Tag is FoundLauncher)
.Select(c => (FoundLauncher)c.Tag)
.ToList();
private void ChangeDir_Click(object sender, RoutedEventArgs e)
{
using var dlg = new System.Windows.Forms.FolderBrowserDialog
{
Description = "Installationsordner wählen",
SelectedPath = Directory.Exists(DirBox.Text) ? DirBox.Text : Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
ShowNewFolderButton = true,
};
if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
var chosen = dlg.SelectedPath;
// wie bei üblichen Installern: Unterordner "AeroMC" anhängen
DirBox.Text = Path.GetFileName(chosen.TrimEnd('\\')).Equals("AeroMC", StringComparison.OrdinalIgnoreCase)
? chosen : Path.Combine(chosen, "AeroMC");
}
// ---------- Installation ----------
private async Task RunInstall(string dir, bool desktop, bool update)
{
_busy = true;
if (!update) ShowPage(PageNumProgress);
SetButtons(false);
var progress = new Progress<(double Value, string Text)>(p => SetProgress(p.Value, p.Text));
try
{
await Task.Run(() => Installer.Install(dir, desktop, progress, waitForExit: update));
_installedExe = Path.Combine(dir, Installer.ExeName);
_done = true;
if (update)
{
ShowSuccess("Update abgeschlossen", $"AeroMC {Installer.Version} wird gestartet …");
StartApp();
_busy = false;
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1.5) };
timer.Tick += (_, _) => { timer.Stop(); Close(); };
timer.Start();
return;
}
// ausgewählte Launcher für den ersten Start vormerken (Fehler hier sollen die Installation nicht kippen)
var selected = SelectedImports();
try { LauncherImport.WritePending(selected); } catch { selected = []; }
ShowSuccess("Alles bereit!", "AeroMC ist installiert. Du findest es im Startmenü" + (desktop ? " und auf dem Desktop." : "."));
if (selected.Count > 0)
ProgressText.Text += $" Beim ersten Start bietet AeroMC dir die Instanzen aus {string.Join(" und ", selected.Select(f => f.Name))} zum Übernehmen an.";
DonePanel.Visibility = Visibility.Visible;
ShowPage(PageNumProgress);
}
catch (Exception ex)
{
ProgressTitle.Text = update ? "Update fehlgeschlagen" : "Installation fehlgeschlagen";
ProgressBarPanel.Visibility = Visibility.Collapsed;
ProgressText.Text = ex.Message;
ProgressText.Foreground = (Brush)FindResource("DangerBrush");
NextButton.Content = "Schließen";
_installedExe = null;
}
_busy = false;
NextButton.IsEnabled = true;
CloseButton.IsEnabled = true;
BackButton.Visibility = Visibility.Hidden;
CancelButton.Visibility = Visibility.Collapsed;
}
/// <summary>Fortschritt ausblenden und den Haken mit kurzem "Aufploppen" zeigen.</summary>
private void ShowSuccess(string title, string text)
{
ProgressBarPanel.Visibility = Visibility.Collapsed;
ProgressTitle.Text = title;
ProgressText.Text = text;
SuccessIcon.Visibility = Visibility.Visible;
var pop = new DoubleAnimation(0.4, 1, TimeSpan.FromMilliseconds(420)) { EasingFunction = new BackEase { EasingMode = EasingMode.EaseOut, Amplitude = 0.5 } };
SuccessScale.BeginAnimation(ScaleTransform.ScaleXProperty, pop);
SuccessScale.BeginAnimation(ScaleTransform.ScaleYProperty, pop);
SuccessIcon.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(200)));
}
private void StartApp()
{
if (_installedExe is null || !File.Exists(_installedExe)) return;
try
{
Process.Start(new ProcessStartInfo(_installedExe)
{
UseShellExecute = true,
WorkingDirectory = Path.GetDirectoryName(_installedExe)!,
});
}
catch { }
}
// ---------- Deinstallation ----------
private async void LoadDataInfo()
{
var root = Installer.DataDir;
var custom = Installer.CustomInstancesDir;
var customNote = custom is null ? "" : $" Instanzen in deinem eigenen Ordner {custom} werden nie angefasst.";
if (!Directory.Exists(root))
{
DataInfo.Text = "Es wurden keine Launcher-Daten gefunden." + customNote;
RemoveDataBox.IsEnabled = false;
return;
}
DataInfo.Text = $"Sie liegen in {root}. Größe wird berechnet …";
var size = await Task.Run(Installer.DataSize);
DataInfo.Text = $"Sie liegen in {root} ({Installer.FormatSize(size)}) und stehen wieder bereit, falls du AeroMC später neu installierst." + customNote;
}
private void RemoveDataBox_Click(object sender, RoutedEventArgs e) =>
RemoveDataWarning.Visibility = RemoveDataBox.IsChecked == true ? Visibility.Visible : Visibility.Collapsed;
private async Task RunUninstall()
{
_busy = true;
bool removeData = RemoveDataBox.IsChecked == true;
ShowOnly(PageProgress);
ProgressTitle.Text = "AeroMC wird entfernt …";
LaunchBox.Visibility = Visibility.Collapsed;
SetButtons(false);
var progress = new Progress<(double Value, string Text)>(p => SetProgress(p.Value, p.Text));
try
{
await Task.Run(() => Installer.Uninstall(removeData, progress));
ShowSuccess("AeroMC wurde entfernt", removeData
? "AeroMC, deine Instanzen, Konten und Einstellungen wurden entfernt (die Daten liegen im Papierkorb)."
: "Deine Instanzen und Einstellungen liegen weiterhin in " + Installer.DataDir + " – falls du AeroMC später wieder installierst.");
}
catch (Exception ex)
{
ProgressTitle.Text = "Deinstallation fehlgeschlagen";
ProgressBarPanel.Visibility = Visibility.Collapsed;
ProgressText.Text = ex.Message;
}
_done = true;
_busy = false;
CloseButton.IsEnabled = true;
NextButton.Content = "Schließen";
NextButton.IsEnabled = true;
CancelButton.Visibility = Visibility.Collapsed;
}
// ---------- Helfer ----------
private void SetButtons(bool enabled)
{
NextButton.IsEnabled = enabled;
BackButton.IsEnabled = enabled;
CancelButton.IsEnabled = enabled;
CloseButton.IsEnabled = enabled;
}
private void SetProgress(double value, string text)
{
if (_done) return;
ProgressText.Text = text;
PercentText.Text = $"{(int)Math.Round(value * 100)} %";
_progress = value;
AnimateProgress();
}
private double _progress;
private void AnimateProgress()
{
var target = Math.Max(0, ProgressTrack.ActualWidth * _progress);
ProgressFill.BeginAnimation(WidthProperty,
new DoubleAnimation(target, TimeSpan.FromMilliseconds(250)) { EasingFunction = new CubicEase() });
}
// Kommt der erste Wert, bevor die Seite fertig angeordnet ist (Breite 0), nachträglich richtig setzen
private void ProgressTrack_SizeChanged(object sender, SizeChangedEventArgs e) => AnimateProgress();
private static bool IsFullPath(string p) =>
p.Length >= 3 && char.IsLetter(p[0]) && p[1] == ':' && (p[2] == '\\' || p[2] == '/');
private void Minimize_Click(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized;
private void Cancel_Click(object sender, RoutedEventArgs e)
{
if (_busy) return;
if (!_done && _mode == SetupMode.Install && _page > 0 &&
MessageBox.Show(this, "Installation wirklich abbrechen?", "AeroMC Setup", MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
return;
Close();
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
if (_busy) e.Cancel = true; // während der Installation nicht schließen
base.OnClosing(e);
}
}