Upload via GUI (93 Dateien)
This commit is contained in:
@@ -268,6 +268,8 @@ public static class Installer
|
|||||||
RemoveAutostart(exe);
|
RemoveAutostart(exe);
|
||||||
Registry.CurrentUser.DeleteSubKeyTree(UninstallKey, throwOnMissingSubKey: false);
|
Registry.CurrentUser.DeleteSubKeyTree(UninstallKey, throwOnMissingSubKey: false);
|
||||||
Registry.CurrentUser.DeleteSubKeyTree(LegacyAppKey, throwOnMissingSubKey: false);
|
Registry.CurrentUser.DeleteSubKeyTree(LegacyAppKey, throwOnMissingSubKey: false);
|
||||||
|
// Name/Logo für Windows-Benachrichtigungen (legt der Launcher selbst an)
|
||||||
|
Registry.CurrentUser.DeleteSubKeyTree(@"Software\Classes\AppUserModelId\" + AppUserModelId, throwOnMissingSubKey: false);
|
||||||
|
|
||||||
if (removeData && Directory.Exists(DataDir))
|
if (removeData && Directory.Exists(DataDir))
|
||||||
{
|
{
|
||||||
|
|||||||
+392
-368
@@ -1,368 +1,392 @@
|
|||||||
<Window x:Class="AeroMCSetup.MainWindow"
|
<Window x:Class="AeroMCSetup.MainWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
Title="AeroMC Setup" Icon="app.ico" Width="780" Height="520" ResizeMode="CanMinimize"
|
Title="AeroMC Setup" Icon="app.ico" Width="780" Height="520" ResizeMode="CanMinimize"
|
||||||
Background="{DynamicResource LayerBrush}" Foreground="{DynamicResource TextBrush}"
|
Background="{DynamicResource LayerBrush}" Foreground="{DynamicResource TextBrush}"
|
||||||
FontFamily="{DynamicResource UiFont}" FontSize="14"
|
FontFamily="{DynamicResource UiFont}" FontSize="14"
|
||||||
TextOptions.TextFormattingMode="Display" UseLayoutRounding="True"
|
TextOptions.TextFormattingMode="Display" UseLayoutRounding="True"
|
||||||
WindowStartupLocation="CenterScreen">
|
WindowStartupLocation="CenterScreen">
|
||||||
|
|
||||||
<WindowChrome.WindowChrome>
|
<WindowChrome.WindowChrome>
|
||||||
<WindowChrome CaptionHeight="36" ResizeBorderThickness="0" GlassFrameThickness="0,0,0,1"
|
<WindowChrome CaptionHeight="36" ResizeBorderThickness="0" GlassFrameThickness="0,0,0,1"
|
||||||
CornerRadius="0" UseAeroCaptionButtons="False"/>
|
CornerRadius="0" UseAeroCaptionButtons="False"/>
|
||||||
</WindowChrome.WindowChrome>
|
</WindowChrome.WindowChrome>
|
||||||
|
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<!-- Markenfarben der Seitenleiste (in Hell und Dunkel gleich) -->
|
<!-- Markenfarben der Seitenleiste (in Hell und Dunkel gleich) -->
|
||||||
<SolidColorBrush x:Key="SideText" Color="#FFFFFF"/>
|
<SolidColorBrush x:Key="SideText" Color="#FFFFFF"/>
|
||||||
<SolidColorBrush x:Key="SideTextSecondary" Color="#B9C8E8"/>
|
<SolidColorBrush x:Key="SideTextSecondary" Color="#B9C8E8"/>
|
||||||
<SolidColorBrush x:Key="SideTextMuted" Color="#7F95C2"/>
|
<SolidColorBrush x:Key="SideTextMuted" Color="#7F95C2"/>
|
||||||
<LinearGradientBrush x:Key="BrandGradient" StartPoint="0,0" EndPoint="1,0">
|
<LinearGradientBrush x:Key="BrandGradient" StartPoint="0,0" EndPoint="1,0">
|
||||||
<GradientStop Color="#2F6FDB" Offset="0"/>
|
<GradientStop Color="#2F6FDB" Offset="0"/>
|
||||||
<GradientStop Color="#4FB8F0" Offset="1"/>
|
<GradientStop Color="#4FB8F0" Offset="1"/>
|
||||||
</LinearGradientBrush>
|
</LinearGradientBrush>
|
||||||
|
|
||||||
<Style x:Key="PageTitle" TargetType="TextBlock">
|
<Style x:Key="PageTitle" TargetType="TextBlock">
|
||||||
<Setter Property="FontFamily" Value="{DynamicResource DisplayFont}"/>
|
<Setter Property="FontFamily" Value="{DynamicResource DisplayFont}"/>
|
||||||
<Setter Property="FontSize" Value="26"/>
|
<Setter Property="FontSize" Value="26"/>
|
||||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
<Setter Property="Margin" Value="0,0,0,8"/>
|
<Setter Property="Margin" Value="0,0,0,8"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style x:Key="Body" TargetType="TextBlock">
|
<Style x:Key="Body" TargetType="TextBlock">
|
||||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||||
<Setter Property="LineHeight" Value="21"/>
|
<Setter Property="LineHeight" Value="21"/>
|
||||||
<Setter Property="Foreground" Value="{DynamicResource TextSecondaryBrush}"/>
|
<Setter Property="Foreground" Value="{DynamicResource TextSecondaryBrush}"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style x:Key="StepDot" TargetType="Border">
|
<Style x:Key="StepDot" TargetType="Border">
|
||||||
<Setter Property="Width" Value="26"/>
|
<Setter Property="Width" Value="26"/>
|
||||||
<Setter Property="Height" Value="26"/>
|
<Setter Property="Height" Value="26"/>
|
||||||
<Setter Property="CornerRadius" Value="13"/>
|
<Setter Property="CornerRadius" Value="13"/>
|
||||||
<Setter Property="BorderThickness" Value="1.5"/>
|
<Setter Property="BorderThickness" Value="1.5"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style x:Key="StepNum" TargetType="TextBlock">
|
<Style x:Key="StepNum" TargetType="TextBlock">
|
||||||
<Setter Property="FontSize" Value="12"/>
|
<Setter Property="FontSize" Value="12"/>
|
||||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style x:Key="StepLabel" TargetType="TextBlock">
|
<Style x:Key="StepLabel" TargetType="TextBlock">
|
||||||
<Setter Property="Margin" Value="12,0,0,0"/>
|
<Setter Property="Margin" Value="12,0,0,0"/>
|
||||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
<Setter Property="FontSize" Value="13.5"/>
|
<Setter Property="FontSize" Value="13.5"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style x:Key="StepLine" TargetType="Border">
|
<Style x:Key="StepLine" TargetType="Border">
|
||||||
<Setter Property="Width" Value="2"/>
|
<Setter Property="Width" Value="2"/>
|
||||||
<Setter Property="Height" Value="18"/>
|
<Setter Property="Height" Value="18"/>
|
||||||
<Setter Property="Margin" Value="12,3,0,3"/>
|
<Setter Property="Margin" Value="12,3,0,3"/>
|
||||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||||
<Setter Property="CornerRadius" Value="1"/>
|
<Setter Property="CornerRadius" Value="1"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style x:Key="FeatureIcon" TargetType="Border">
|
<Style x:Key="FeatureIcon" TargetType="Border">
|
||||||
<Setter Property="Width" Value="38"/>
|
<Setter Property="Width" Value="38"/>
|
||||||
<Setter Property="Height" Value="38"/>
|
<Setter Property="Height" Value="38"/>
|
||||||
<Setter Property="CornerRadius" Value="10"/>
|
<Setter Property="CornerRadius" Value="10"/>
|
||||||
<Setter Property="Background" Value="{DynamicResource AccentSubtleBrush}"/>
|
<Setter Property="Background" Value="{DynamicResource AccentSubtleBrush}"/>
|
||||||
<Setter Property="VerticalAlignment" Value="Top"/>
|
<Setter Property="VerticalAlignment" Value="Top"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style x:Key="FeatureGlyph" TargetType="TextBlock">
|
<Style x:Key="FeatureGlyph" TargetType="TextBlock">
|
||||||
<Setter Property="FontFamily" Value="{DynamicResource IconFont}"/>
|
<Setter Property="FontFamily" Value="{DynamicResource IconFont}"/>
|
||||||
<Setter Property="FontSize" Value="17"/>
|
<Setter Property="FontSize" Value="17"/>
|
||||||
<Setter Property="Foreground" Value="{DynamicResource AccentBrush}"/>
|
<Setter Property="Foreground" Value="{DynamicResource AccentBrush}"/>
|
||||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style x:Key="FeatureTitle" TargetType="TextBlock">
|
<Style x:Key="FeatureTitle" TargetType="TextBlock">
|
||||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
<Setter Property="FontSize" Value="13.5"/>
|
<Setter Property="FontSize" Value="13.5"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style x:Key="FeatureText" TargetType="TextBlock">
|
<Style x:Key="FeatureText" TargetType="TextBlock">
|
||||||
<Setter Property="FontSize" Value="12"/>
|
<Setter Property="FontSize" Value="12"/>
|
||||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||||
<Setter Property="Margin" Value="0,2,0,0"/>
|
<Setter Property="Margin" Value="0,2,0,0"/>
|
||||||
<Setter Property="Foreground" Value="{DynamicResource TextSecondaryBrush}"/>
|
<Setter Property="Foreground" Value="{DynamicResource TextSecondaryBrush}"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style x:Key="Card" TargetType="Border">
|
<Style x:Key="Card" TargetType="Border">
|
||||||
<Setter Property="Padding" Value="16,14"/>
|
<Setter Property="Padding" Value="16,14"/>
|
||||||
<Setter Property="CornerRadius" Value="8"/>
|
<Setter Property="CornerRadius" Value="8"/>
|
||||||
<Setter Property="Background" Value="{DynamicResource ControlFillBrush}"/>
|
<Setter Property="Background" Value="{DynamicResource ControlFillBrush}"/>
|
||||||
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeBrush}"/>
|
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeBrush}"/>
|
||||||
<Setter Property="BorderThickness" Value="1"/>
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
</Style>
|
</Style>
|
||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="36"/>
|
<RowDefinition Height="36"/>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="250"/>
|
<ColumnDefinition Width="250"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- ===== Seitenleiste im AeroMC-Look ===== -->
|
<!-- ===== Seitenleiste im AeroMC-Look ===== -->
|
||||||
<Border Grid.RowSpan="3" ClipToBounds="True">
|
<Border Grid.RowSpan="3" ClipToBounds="True">
|
||||||
<Border.Background>
|
<Border.Background>
|
||||||
<LinearGradientBrush StartPoint="0,0" EndPoint="0.6,1">
|
<LinearGradientBrush StartPoint="0,0" EndPoint="0.6,1">
|
||||||
<GradientStop Color="#0C1630" Offset="0"/>
|
<GradientStop Color="#0C1630" Offset="0"/>
|
||||||
<GradientStop Color="#112452" Offset="0.55"/>
|
<GradientStop Color="#112452" Offset="0.55"/>
|
||||||
<GradientStop Color="#173A86" Offset="1"/>
|
<GradientStop Color="#173A86" Offset="1"/>
|
||||||
</LinearGradientBrush>
|
</LinearGradientBrush>
|
||||||
</Border.Background>
|
</Border.Background>
|
||||||
<Grid>
|
<Grid>
|
||||||
<!-- Lichtschein hinter dem Logo und unten -->
|
<!-- Lichtschein hinter dem Logo und unten -->
|
||||||
<Ellipse Width="320" Height="320" Margin="-120,-110,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" IsHitTestVisible="False">
|
<Ellipse Width="320" Height="320" Margin="-120,-110,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" IsHitTestVisible="False">
|
||||||
<Ellipse.Fill>
|
<Ellipse.Fill>
|
||||||
<RadialGradientBrush>
|
<RadialGradientBrush>
|
||||||
<GradientStop Color="#664A86EA" Offset="0"/>
|
<GradientStop Color="#664A86EA" Offset="0"/>
|
||||||
<GradientStop Color="#004A86EA" Offset="1"/>
|
<GradientStop Color="#004A86EA" Offset="1"/>
|
||||||
</RadialGradientBrush>
|
</RadialGradientBrush>
|
||||||
</Ellipse.Fill>
|
</Ellipse.Fill>
|
||||||
</Ellipse>
|
</Ellipse>
|
||||||
<Ellipse Width="300" Height="300" Margin="0,0,-150,-150" HorizontalAlignment="Right" VerticalAlignment="Bottom" IsHitTestVisible="False">
|
<Ellipse Width="300" Height="300" Margin="0,0,-150,-150" HorizontalAlignment="Right" VerticalAlignment="Bottom" IsHitTestVisible="False">
|
||||||
<Ellipse.Fill>
|
<Ellipse.Fill>
|
||||||
<RadialGradientBrush>
|
<RadialGradientBrush>
|
||||||
<GradientStop Color="#444FB8F0" Offset="0"/>
|
<GradientStop Color="#444FB8F0" Offset="0"/>
|
||||||
<GradientStop Color="#004FB8F0" Offset="1"/>
|
<GradientStop Color="#004FB8F0" Offset="1"/>
|
||||||
</RadialGradientBrush>
|
</RadialGradientBrush>
|
||||||
</Ellipse.Fill>
|
</Ellipse.Fill>
|
||||||
</Ellipse>
|
</Ellipse>
|
||||||
|
|
||||||
<StackPanel Margin="30,64,24,0">
|
<StackPanel Margin="30,64,24,0">
|
||||||
<Image Source="app.png" Width="68" Height="68" HorizontalAlignment="Left" RenderOptions.BitmapScalingMode="HighQuality">
|
<Image Source="app.png" Width="68" Height="68" HorizontalAlignment="Left" RenderOptions.BitmapScalingMode="HighQuality">
|
||||||
<Image.Effect>
|
<Image.Effect>
|
||||||
<DropShadowEffect Color="#4A86EA" BlurRadius="28" ShadowDepth="0" Opacity="0.75"/>
|
<DropShadowEffect Color="#4A86EA" BlurRadius="28" ShadowDepth="0" Opacity="0.75"/>
|
||||||
</Image.Effect>
|
</Image.Effect>
|
||||||
</Image>
|
</Image>
|
||||||
<TextBlock Text="AeroMC" FontFamily="{DynamicResource DisplayFont}" FontSize="26" FontWeight="SemiBold"
|
<TextBlock Text="AeroMC" FontFamily="{DynamicResource DisplayFont}" FontSize="26" FontWeight="SemiBold"
|
||||||
Foreground="{StaticResource SideText}" Margin="0,16,0,0"/>
|
Foreground="{StaticResource SideText}" Margin="0,16,0,0"/>
|
||||||
<TextBlock x:Name="VersionText" Foreground="{StaticResource SideTextSecondary}" FontSize="12" Margin="0,2,0,0"/>
|
<TextBlock x:Name="VersionText" Foreground="{StaticResource SideTextSecondary}" FontSize="12" Margin="0,2,0,0"/>
|
||||||
|
|
||||||
<StackPanel x:Name="Steps" Margin="0,38,0,0">
|
<StackPanel x:Name="Steps" Margin="0,38,0,0">
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Border x:Name="Dot0" Style="{StaticResource StepDot}"><TextBlock x:Name="Num0" Text="1" Style="{StaticResource StepNum}"/></Border>
|
<Border x:Name="Dot0" Style="{StaticResource StepDot}"><TextBlock x:Name="Num0" Text="1" Style="{StaticResource StepNum}"/></Border>
|
||||||
<TextBlock x:Name="Label0" Text="Willkommen" Style="{StaticResource StepLabel}"/>
|
<TextBlock x:Name="Label0" Text="Willkommen" Style="{StaticResource StepLabel}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Border x:Name="Line0" Style="{StaticResource StepLine}"/>
|
<Border x:Name="Line0" Style="{StaticResource StepLine}"/>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Border x:Name="Dot1" Style="{StaticResource StepDot}"><TextBlock x:Name="Num1" Text="2" Style="{StaticResource StepNum}"/></Border>
|
<Border x:Name="Dot1" Style="{StaticResource StepDot}"><TextBlock x:Name="Num1" Text="2" Style="{StaticResource StepNum}"/></Border>
|
||||||
<TextBlock x:Name="Label1" Text="Lizenz" Style="{StaticResource StepLabel}"/>
|
<TextBlock x:Name="Label1" Text="Lizenz" Style="{StaticResource StepLabel}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Border x:Name="Line1" Style="{StaticResource StepLine}"/>
|
<Border x:Name="Line1" Style="{StaticResource StepLine}"/>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Border x:Name="Dot2" Style="{StaticResource StepDot}"><TextBlock x:Name="Num2" Text="3" Style="{StaticResource StepNum}"/></Border>
|
<Border x:Name="Dot2" Style="{StaticResource StepDot}"><TextBlock x:Name="Num2" Text="3" Style="{StaticResource StepNum}"/></Border>
|
||||||
<TextBlock x:Name="Label2" Text="Optionen" Style="{StaticResource StepLabel}"/>
|
<TextBlock x:Name="Label2" Text="Optionen" Style="{StaticResource StepLabel}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Border x:Name="Line2" Style="{StaticResource StepLine}"/>
|
<Border x:Name="Line2" Style="{StaticResource StepLine}"/>
|
||||||
<!-- Import: nur sichtbar, wenn andere Launcher gefunden wurden -->
|
<!-- Import: nur sichtbar, wenn andere Launcher gefunden wurden -->
|
||||||
<StackPanel x:Name="ImportStepRow" Orientation="Horizontal">
|
<StackPanel x:Name="ImportStepRow" Orientation="Horizontal">
|
||||||
<Border x:Name="Dot3" Style="{StaticResource StepDot}"><TextBlock x:Name="Num3" Text="4" Style="{StaticResource StepNum}"/></Border>
|
<Border x:Name="Dot3" Style="{StaticResource StepDot}"><TextBlock x:Name="Num3" Text="4" Style="{StaticResource StepNum}"/></Border>
|
||||||
<TextBlock x:Name="Label3" Text="Import" Style="{StaticResource StepLabel}"/>
|
<TextBlock x:Name="Label3" Text="Import" Style="{StaticResource StepLabel}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Border x:Name="Line3" Style="{StaticResource StepLine}"/>
|
<Border x:Name="Line3" Style="{StaticResource StepLine}"/>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Border x:Name="Dot4" Style="{StaticResource StepDot}"><TextBlock x:Name="Num4" Text="5" Style="{StaticResource StepNum}"/></Border>
|
<Border x:Name="Dot4" Style="{StaticResource StepDot}"><TextBlock x:Name="Num4" Text="5" Style="{StaticResource StepNum}"/></Border>
|
||||||
<TextBlock x:Name="Label4" Text="Installation" Style="{StaticResource StepLabel}"/>
|
<TextBlock x:Name="Label4" Text="Installation" Style="{StaticResource StepLabel}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock Text="© M_Viper · m-viper.de" FontSize="11" Foreground="{StaticResource SideTextMuted}"
|
<TextBlock Text="© M_Viper · m-viper.de" FontSize="11" Foreground="{StaticResource SideTextMuted}"
|
||||||
VerticalAlignment="Bottom" Margin="30,0,0,20"/>
|
VerticalAlignment="Bottom" Margin="30,0,0,20"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Titelleiste -->
|
<!-- Titelleiste -->
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="14,0,0,0">
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="14,0,0,0">
|
||||||
<Image Source="app.ico" Width="16" Height="16" RenderOptions.BitmapScalingMode="HighQuality"/>
|
<Image Source="app.ico" Width="16" Height="16" RenderOptions.BitmapScalingMode="HighQuality"/>
|
||||||
<TextBlock x:Name="WindowTitle" Text="AeroMC Setup" FontSize="12" Margin="10,0,0,0" VerticalAlignment="Center"
|
<TextBlock x:Name="WindowTitle" Text="AeroMC Setup" FontSize="12" Margin="10,0,0,0" VerticalAlignment="Center"
|
||||||
Foreground="{StaticResource SideTextSecondary}"/>
|
Foreground="{StaticResource SideTextSecondary}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top">
|
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top">
|
||||||
<Button Style="{StaticResource CaptionButton}" Content="" Click="Minimize_Click"
|
<Button Style="{StaticResource CaptionButton}" Content="" Click="Minimize_Click"
|
||||||
WindowChrome.IsHitTestVisibleInChrome="True"/>
|
WindowChrome.IsHitTestVisibleInChrome="True"/>
|
||||||
<Button x:Name="CloseButton" Style="{StaticResource CloseCaptionButton}" Content="" Click="Cancel_Click"
|
<Button x:Name="CloseButton" Style="{StaticResource CloseCaptionButton}" Content="" Click="Cancel_Click"
|
||||||
WindowChrome.IsHitTestVisibleInChrome="True"/>
|
WindowChrome.IsHitTestVisibleInChrome="True"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- ===== Inhalt ===== -->
|
<!-- ===== Inhalt ===== -->
|
||||||
<Grid Grid.Row="1" Grid.Column="1" Margin="40,18,40,0">
|
<Grid Grid.Row="1" Grid.Column="1" Margin="40,18,40,0">
|
||||||
|
|
||||||
<!-- 0: Willkommen -->
|
<!-- 0: Willkommen -->
|
||||||
<StackPanel x:Name="PageWelcome">
|
<StackPanel x:Name="PageWelcome">
|
||||||
<TextBlock Text="Willkommen bei AeroMC" Style="{StaticResource PageTitle}"/>
|
<TextBlock Text="Willkommen bei AeroMC" Style="{StaticResource PageTitle}"/>
|
||||||
<TextBlock Style="{StaticResource Body}"
|
<TextBlock Style="{StaticResource Body}"
|
||||||
Text="Der Assistent richtet AeroMC in wenigen Schritten ein – nur für dein Benutzerkonto und ohne Administratorrechte."/>
|
Text="Der Assistent richtet AeroMC in wenigen Schritten ein – nur für dein Benutzerkonto und ohne Administratorrechte."/>
|
||||||
|
|
||||||
<Grid Margin="0,24,0,0">
|
<Grid Margin="0,24,0,0">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
<ColumnDefinition Width="20"/>
|
<ColumnDefinition Width="20"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="18"/>
|
<RowDefinition Height="18"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<Border Style="{StaticResource FeatureIcon}" DockPanel.Dock="Left"><TextBlock Text="" Style="{StaticResource FeatureGlyph}"/></Border>
|
<Border Style="{StaticResource FeatureIcon}" DockPanel.Dock="Left"><TextBlock Text="" Style="{StaticResource FeatureGlyph}"/></Border>
|
||||||
<StackPanel Margin="12,0,0,0">
|
<StackPanel Margin="12,0,0,0">
|
||||||
<TextBlock Text="Getrennte Instanzen" Style="{StaticResource FeatureTitle}"/>
|
<TextBlock Text="Getrennte Instanzen" Style="{StaticResource FeatureTitle}"/>
|
||||||
<TextBlock Text="Mods und Modpacks von Modrinth und CurseForge" Style="{StaticResource FeatureText}"/>
|
<TextBlock Text="Mods und Modpacks von Modrinth und CurseForge" Style="{StaticResource FeatureText}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<DockPanel Grid.Column="2">
|
<DockPanel Grid.Column="2">
|
||||||
<Border Style="{StaticResource FeatureIcon}" DockPanel.Dock="Left"><TextBlock Text="" Style="{StaticResource FeatureGlyph}"/></Border>
|
<Border Style="{StaticResource FeatureIcon}" DockPanel.Dock="Left"><TextBlock Text="" Style="{StaticResource FeatureGlyph}"/></Border>
|
||||||
<StackPanel Margin="12,0,0,0">
|
<StackPanel Margin="12,0,0,0">
|
||||||
<TextBlock Text="Spielaufzeichnung" Style="{StaticResource FeatureTitle}"/>
|
<TextBlock Text="Spielaufzeichnung" Style="{StaticResource FeatureTitle}"/>
|
||||||
<TextBlock Text="Als MP4, ganz ohne Zusatzprogramm" Style="{StaticResource FeatureText}"/>
|
<TextBlock Text="Als MP4, ganz ohne Zusatzprogramm" Style="{StaticResource FeatureText}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<DockPanel Grid.Row="2">
|
<DockPanel Grid.Row="2">
|
||||||
<Border Style="{StaticResource FeatureIcon}" DockPanel.Dock="Left"><TextBlock Text="" Style="{StaticResource FeatureGlyph}"/></Border>
|
<Border Style="{StaticResource FeatureIcon}" DockPanel.Dock="Left"><TextBlock Text="" Style="{StaticResource FeatureGlyph}"/></Border>
|
||||||
<StackPanel Margin="12,0,0,0">
|
<StackPanel Margin="12,0,0,0">
|
||||||
<TextBlock Text="Freunde" Style="{StaticResource FeatureTitle}"/>
|
<TextBlock Text="Freunde" Style="{StaticResource FeatureTitle}"/>
|
||||||
<TextBlock Text="Online-Status und Meldungen im Spiel" Style="{StaticResource FeatureText}"/>
|
<TextBlock Text="Online-Status und Meldungen im Spiel" Style="{StaticResource FeatureText}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<DockPanel Grid.Row="2" Grid.Column="2">
|
<DockPanel Grid.Row="2" Grid.Column="2">
|
||||||
<Border Style="{StaticResource FeatureIcon}" DockPanel.Dock="Left"><TextBlock Text="" Style="{StaticResource FeatureGlyph}"/></Border>
|
<Border Style="{StaticResource FeatureIcon}" DockPanel.Dock="Left"><TextBlock Text="" Style="{StaticResource FeatureGlyph}"/></Border>
|
||||||
<StackPanel Margin="12,0,0,0">
|
<StackPanel Margin="12,0,0,0">
|
||||||
<TextBlock Text="Microsoft-Konten" Style="{StaticResource FeatureTitle}"/>
|
<TextBlock Text="Microsoft-Konten" Style="{StaticResource FeatureTitle}"/>
|
||||||
<TextBlock Text="Mehrere Konten, Skins direkt ändern" Style="{StaticResource FeatureText}"/>
|
<TextBlock Text="Mehrere Konten, Skins direkt ändern" Style="{StaticResource FeatureText}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Border x:Name="UpdateHint" Margin="0,26,0,0" Padding="14,10" CornerRadius="8" Visibility="Collapsed"
|
<Border x:Name="UpdateHint" Margin="0,26,0,0" Padding="14,10" CornerRadius="8" Visibility="Collapsed"
|
||||||
Background="{DynamicResource AccentSubtleBrush}" BorderBrush="{DynamicResource AccentSubtleStrokeBrush}" BorderThickness="1">
|
Background="{DynamicResource AccentSubtleBrush}" BorderBrush="{DynamicResource AccentSubtleStrokeBrush}" BorderThickness="1">
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<TextBlock DockPanel.Dock="Left" Text="" FontFamily="{DynamicResource IconFont}" FontSize="16"
|
<TextBlock DockPanel.Dock="Left" Text="" FontFamily="{DynamicResource IconFont}" FontSize="16"
|
||||||
Foreground="{DynamicResource AccentBrush}" Margin="0,2,12,0" VerticalAlignment="Top"/>
|
Foreground="{DynamicResource AccentBrush}" Margin="0,2,12,0" VerticalAlignment="Top"/>
|
||||||
<TextBlock x:Name="UpdateHintText" TextWrapping="Wrap" LineHeight="20" FontSize="13"/>
|
<TextBlock x:Name="UpdateHintText" TextWrapping="Wrap" LineHeight="20" FontSize="13"/>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<Border x:Name="RunningHint" Margin="0,10,0,0" Padding="14,10" CornerRadius="8" Visibility="Collapsed"
|
<Border x:Name="RunningHint" Margin="0,10,0,0" Padding="14,10" CornerRadius="8" Visibility="Collapsed"
|
||||||
Background="{DynamicResource ControlFillBrush}" BorderBrush="{DynamicResource ControlStrokeBrush}" BorderThickness="1">
|
Background="{DynamicResource ControlFillBrush}" BorderBrush="{DynamicResource ControlStrokeBrush}" BorderThickness="1">
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<TextBlock DockPanel.Dock="Left" Text="" FontFamily="{DynamicResource IconFont}" FontSize="16"
|
<TextBlock DockPanel.Dock="Left" Text="" FontFamily="{DynamicResource IconFont}" FontSize="16"
|
||||||
Foreground="{DynamicResource AccentBrush}" Margin="0,2,12,0" VerticalAlignment="Top"/>
|
Foreground="{DynamicResource AccentBrush}" Margin="0,2,12,0" VerticalAlignment="Top"/>
|
||||||
<TextBlock TextWrapping="Wrap" LineHeight="20" FontSize="13"
|
<TextBlock TextWrapping="Wrap" LineHeight="20" FontSize="13"
|
||||||
Text="AeroMC läuft gerade und wird für die Installation automatisch beendet."/>
|
Text="AeroMC läuft gerade und wird für die Installation automatisch beendet."/>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- 1: Lizenz -->
|
<!-- 1: Lizenz -->
|
||||||
<DockPanel x:Name="PageLicense" Visibility="Collapsed">
|
<DockPanel x:Name="PageLicense" Visibility="Collapsed">
|
||||||
<TextBlock DockPanel.Dock="Top" Text="Lizenz" Style="{StaticResource PageTitle}"/>
|
<TextBlock DockPanel.Dock="Top" Text="Lizenz" Style="{StaticResource PageTitle}"/>
|
||||||
<TextBlock DockPanel.Dock="Top" Style="{StaticResource Body}" Margin="0,0,0,14"
|
<TextBlock DockPanel.Dock="Top" Style="{StaticResource Body}" Margin="0,0,0,14"
|
||||||
Text="Bitte lies die Nutzungsbedingungen. AeroMC ist kostenlos für den privaten Gebrauch."/>
|
Text="Bitte lies die Nutzungsbedingungen. AeroMC ist kostenlos für den privaten Gebrauch."/>
|
||||||
<CheckBox x:Name="AcceptLicense" DockPanel.Dock="Bottom" Margin="0,14,0,0"
|
<CheckBox x:Name="AcceptLicense" DockPanel.Dock="Bottom" Margin="0,14,0,0"
|
||||||
Checked="AcceptLicense_Click" Unchecked="AcceptLicense_Click"
|
Checked="AcceptLicense_Click" Unchecked="AcceptLicense_Click"
|
||||||
Content="Ich akzeptiere die Lizenz- und Nutzungsbedingungen"/>
|
Content="Ich akzeptiere die Lizenz- und Nutzungsbedingungen"/>
|
||||||
<Border Background="{DynamicResource ControlFillBrush}" BorderBrush="{DynamicResource ControlStrokeBrush}"
|
<Border Background="{DynamicResource ControlFillBrush}" BorderBrush="{DynamicResource ControlStrokeBrush}"
|
||||||
BorderThickness="1" CornerRadius="8">
|
BorderThickness="1" CornerRadius="8">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
<TextBlock x:Name="LicenseText" TextWrapping="Wrap" FontSize="12.5" LineHeight="18" Margin="16,12"
|
<!-- wird aus LICENSE aufgebaut: Überschriften, Absätze, Aufzählungen (siehe ZeigeLizenz) -->
|
||||||
Foreground="{DynamicResource TextSecondaryBrush}"/>
|
<StackPanel x:Name="LicensePanel" Margin="16,12,16,14"/>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Border>
|
</Border>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
<!-- 2: Optionen -->
|
<!-- 2: Optionen -->
|
||||||
<StackPanel x:Name="PageOptions" Visibility="Collapsed">
|
<StackPanel x:Name="PageOptions" Visibility="Collapsed">
|
||||||
<TextBlock Text="Optionen" Style="{StaticResource PageTitle}"/>
|
<TextBlock Text="Optionen" Style="{StaticResource PageTitle}"/>
|
||||||
<TextBlock Style="{StaticResource Body}" Text="Wohin soll AeroMC installiert werden?"/>
|
<TextBlock Style="{StaticResource Body}" Text="Wohin soll AeroMC installiert werden?"/>
|
||||||
|
|
||||||
<Border Style="{StaticResource Card}" Margin="0,18,0,0">
|
<Border Style="{StaticResource Card}" Margin="0,18,0,0">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Text="Installationsordner" FontWeight="SemiBold" FontSize="13" Margin="0,0,0,10"/>
|
<TextBlock Text="Installationsordner" FontWeight="SemiBold" FontSize="13" Margin="0,0,0,10"/>
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<Button DockPanel.Dock="Right" Content="Ändern …" Margin="8,0,0,0" Click="ChangeDir_Click"/>
|
<Button DockPanel.Dock="Right" Content="Ändern …" Margin="8,0,0,0" Click="ChangeDir_Click"/>
|
||||||
<TextBox x:Name="DirBox"/>
|
<TextBox x:Name="DirBox"/>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<TextBlock Style="{StaticResource Body}" FontSize="12" LineHeight="18" Margin="0,10,0,0"
|
<TextBlock Style="{StaticResource Body}" FontSize="12" LineHeight="18" Margin="0,10,0,0"
|
||||||
Text="Benötigt ca. 400 MB. Deine Instanzen, Welten und Einstellungen liegen getrennt davon und bleiben bei Updates erhalten."/>
|
Text="Benötigt ca. 400 MB. Deine Instanzen, Welten und Einstellungen liegen getrennt davon und bleiben bei Updates erhalten."/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<Border Style="{StaticResource Card}" Margin="0,12,0,0">
|
<Border Style="{StaticResource Card}" Margin="0,12,0,0">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<CheckBox x:Name="DesktopLinkBox" Content="Verknüpfung auf dem Desktop" IsChecked="True"/>
|
<CheckBox x:Name="DesktopLinkBox" Content="Verknüpfung auf dem Desktop" IsChecked="True"/>
|
||||||
<CheckBox x:Name="StartMenuBox" Content="Eintrag im Startmenü" Margin="0,12,0,0" IsChecked="True" IsEnabled="False"/>
|
</StackPanel>
|
||||||
</StackPanel>
|
</Border>
|
||||||
</Border>
|
</StackPanel>
|
||||||
</StackPanel>
|
|
||||||
|
<!-- 3: Import aus anderen Launchern (optional) -->
|
||||||
<!-- 3: Import aus anderen Launchern (optional) -->
|
<StackPanel x:Name="PageImport" Visibility="Collapsed">
|
||||||
<StackPanel x:Name="PageImport" Visibility="Collapsed">
|
<TextBlock Text="Import" Style="{StaticResource PageTitle}"/>
|
||||||
<TextBlock Text="Import" Style="{StaticResource PageTitle}"/>
|
<TextBlock Style="{StaticResource Body}"
|
||||||
<TextBlock Style="{StaticResource Body}"
|
Text="Optional: Diese Launcher wurden auf deinem PC gefunden. Was du ankreuzt, bietet AeroMC beim ersten Start zum Übernehmen an – dort wählst du die einzelnen Instanzen aus."/>
|
||||||
Text="Optional: Diese Launcher wurden auf deinem PC gefunden. Was du ankreuzt, bietet AeroMC beim ersten Start zum Übernehmen an – dort wählst du die einzelnen Instanzen aus."/>
|
<Border Style="{StaticResource Card}" Margin="0,18,0,0" Padding="16,6">
|
||||||
<Border Style="{StaticResource Card}" Margin="0,18,0,0" Padding="16,6">
|
<StackPanel x:Name="ImportList"/>
|
||||||
<StackPanel x:Name="ImportList"/>
|
</Border>
|
||||||
</Border>
|
<TextBlock Style="{StaticResource Body}" FontSize="12" LineHeight="18" Margin="0,12,0,0"
|
||||||
<TextBlock Style="{StaticResource Body}" FontSize="12" LineHeight="18" Margin="0,12,0,0"
|
Text="In den anderen Launchern wird nichts verändert – AeroMC legt Kopien an. Später geht das jederzeit über „Importieren“ in der Toolleiste."/>
|
||||||
Text="In den anderen Launchern wird nichts verändert – AeroMC legt Kopien an. Später geht das jederzeit über „Importieren“ in der Toolleiste."/>
|
</StackPanel>
|
||||||
</StackPanel>
|
|
||||||
|
<!-- 4: Fortschritt / Fertig -->
|
||||||
<!-- 4: Fortschritt / Fertig -->
|
<StackPanel x:Name="PageProgress" Visibility="Collapsed">
|
||||||
<StackPanel x:Name="PageProgress" Visibility="Collapsed">
|
<Border x:Name="SuccessIcon" Width="64" Height="64" CornerRadius="32" HorizontalAlignment="Left" Margin="0,4,0,20"
|
||||||
<Border x:Name="SuccessIcon" Width="64" Height="64" CornerRadius="32" HorizontalAlignment="Left" Margin="0,4,0,20"
|
Background="{StaticResource BrandGradient}" RenderTransformOrigin="0.5,0.5" Visibility="Collapsed">
|
||||||
Background="{StaticResource BrandGradient}" RenderTransformOrigin="0.5,0.5" Visibility="Collapsed">
|
<Border.RenderTransform><ScaleTransform x:Name="SuccessScale" ScaleX="1" ScaleY="1"/></Border.RenderTransform>
|
||||||
<Border.RenderTransform><ScaleTransform x:Name="SuccessScale" ScaleX="1" ScaleY="1"/></Border.RenderTransform>
|
<Border.Effect><DropShadowEffect Color="#4A86EA" BlurRadius="24" ShadowDepth="0" Opacity="0.55"/></Border.Effect>
|
||||||
<Border.Effect><DropShadowEffect Color="#4A86EA" BlurRadius="24" ShadowDepth="0" Opacity="0.55"/></Border.Effect>
|
<TextBlock Text="" FontFamily="{DynamicResource IconFont}" FontSize="26" Foreground="White"
|
||||||
<TextBlock Text="" FontFamily="{DynamicResource IconFont}" FontSize="26" Foreground="White"
|
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
</Border>
|
||||||
</Border>
|
<TextBlock x:Name="ProgressTitle" Text="AeroMC wird installiert …" Style="{StaticResource PageTitle}"/>
|
||||||
<TextBlock x:Name="ProgressTitle" Text="AeroMC wird installiert …" Style="{StaticResource PageTitle}"/>
|
|
||||||
|
<StackPanel x:Name="ProgressBarPanel" Margin="0,18,0,0">
|
||||||
<StackPanel x:Name="ProgressBarPanel" Margin="0,18,0,0">
|
<TextBlock x:Name="PercentText" Text="0 %" FontFamily="{DynamicResource DisplayFont}" FontSize="44" FontWeight="SemiBold"
|
||||||
<TextBlock x:Name="PercentText" Text="0 %" FontFamily="{DynamicResource DisplayFont}" FontSize="44" FontWeight="SemiBold"
|
Foreground="{DynamicResource AccentBrush}"/>
|
||||||
Foreground="{DynamicResource AccentBrush}"/>
|
<Border x:Name="ProgressTrack" SizeChanged="ProgressTrack_SizeChanged" Height="8" CornerRadius="4" Margin="0,10,0,0" Background="{DynamicResource SubtleSelectedBrush}">
|
||||||
<Border x:Name="ProgressTrack" SizeChanged="ProgressTrack_SizeChanged" Height="8" CornerRadius="4" Margin="0,10,0,0" Background="{DynamicResource SubtleSelectedBrush}">
|
<Border x:Name="ProgressFill" HorizontalAlignment="Left" Width="0" CornerRadius="4" Background="{StaticResource BrandGradient}"/>
|
||||||
<Border x:Name="ProgressFill" HorizontalAlignment="Left" Width="0" CornerRadius="4" Background="{StaticResource BrandGradient}"/>
|
</Border>
|
||||||
</Border>
|
</StackPanel>
|
||||||
</StackPanel>
|
<TextBlock x:Name="ProgressText" Style="{StaticResource Body}" Margin="0,12,0,0" Text="Einen Moment bitte."/>
|
||||||
<TextBlock x:Name="ProgressText" Style="{StaticResource Body}" Margin="0,12,0,0" Text="Einen Moment bitte."/>
|
|
||||||
|
<StackPanel x:Name="DonePanel" Visibility="Collapsed">
|
||||||
<StackPanel x:Name="DonePanel" Visibility="Collapsed">
|
<CheckBox x:Name="LaunchBox" Content="AeroMC jetzt starten" IsChecked="True" Margin="0,24,0,0"/>
|
||||||
<CheckBox x:Name="LaunchBox" Content="AeroMC jetzt starten" IsChecked="True" Margin="0,24,0,0"/>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
|
||||||
|
<!-- Deinstallation -->
|
||||||
<!-- Deinstallation -->
|
<StackPanel x:Name="PageUninstall" Visibility="Collapsed">
|
||||||
<StackPanel x:Name="PageUninstall" Visibility="Collapsed">
|
<TextBlock Text="AeroMC deinstallieren" Style="{StaticResource PageTitle}"/>
|
||||||
<TextBlock Text="AeroMC deinstallieren" Style="{StaticResource PageTitle}"/>
|
<TextBlock Style="{StaticResource Body}"
|
||||||
<TextBlock Style="{StaticResource Body}"
|
Text="AeroMC wird von deinem PC entfernt: Programm, Verknüpfungen, Autostart und der Eintrag in den Windows-Einstellungen."/>
|
||||||
Text="AeroMC wird von deinem PC entfernt: Programm, Verknüpfungen, Autostart und der Eintrag in den Windows-Einstellungen."/>
|
<Border Style="{StaticResource Card}" Margin="0,22,0,0">
|
||||||
<Border Style="{StaticResource Card}" Margin="0,22,0,0">
|
<StackPanel>
|
||||||
<StackPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<StackPanel Orientation="Horizontal">
|
<TextBlock Text="" FontFamily="{DynamicResource IconFont}" FontSize="15" Foreground="{DynamicResource AccentBrush}"
|
||||||
<TextBlock Text="" FontFamily="{DynamicResource IconFont}" FontSize="15" Foreground="{DynamicResource AccentBrush}"
|
VerticalAlignment="Center"/>
|
||||||
VerticalAlignment="Center"/>
|
<TextBlock Text="Deine Instanzen und Welten bleiben erhalten" FontWeight="SemiBold" Margin="10,0,0,0" VerticalAlignment="Center"/>
|
||||||
<TextBlock Text="Deine Instanzen und Welten bleiben erhalten" FontWeight="SemiBold" Margin="10,0,0,0" VerticalAlignment="Center"/>
|
</StackPanel>
|
||||||
</StackPanel>
|
<TextBlock x:Name="DataInfo" Style="{StaticResource Body}" FontSize="12.5" Margin="0,6,0,14"/>
|
||||||
<TextBlock x:Name="DataInfo" Style="{StaticResource Body}" FontSize="12.5" Margin="0,6,0,14"/>
|
<CheckBox x:Name="RemoveDataBox" Content="Auch Instanzen, Welten, Konten und Einstellungen löschen"
|
||||||
<CheckBox x:Name="RemoveDataBox" Content="Auch Instanzen, Welten, Konten und Einstellungen löschen"
|
Checked="RemoveDataBox_Click" Unchecked="RemoveDataBox_Click"/>
|
||||||
Checked="RemoveDataBox_Click" Unchecked="RemoveDataBox_Click"/>
|
<TextBlock x:Name="RemoveDataWarning" Visibility="Collapsed" Margin="30,8,0,0" TextWrapping="Wrap" FontSize="12.5"
|
||||||
<TextBlock x:Name="RemoveDataWarning" Visibility="Collapsed" Margin="30,8,0,0" TextWrapping="Wrap" FontSize="12.5"
|
Foreground="{DynamicResource DangerBrush}"
|
||||||
Foreground="{DynamicResource DangerBrush}"
|
Text="Achtung: Damit wandern alle Welten und Spielstände aus AeroMC in den Papierkorb. Aufnahmen (Videos) und Backups (Dokumente) bleiben erhalten."/>
|
||||||
Text="Achtung: Damit wandern alle Welten und Spielstände aus AeroMC in den Papierkorb. Aufnahmen (Videos) und Backups (Dokumente) bleiben erhalten."/>
|
</StackPanel>
|
||||||
</StackPanel>
|
</Border>
|
||||||
</Border>
|
</StackPanel>
|
||||||
</StackPanel>
|
</Grid>
|
||||||
</Grid>
|
|
||||||
|
<!-- Fußleiste -->
|
||||||
<!-- Fußleiste -->
|
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,14,40,24">
|
||||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,14,40,24">
|
<Button x:Name="BackButton" Content="Zurück" Width="110" Margin="0,0,8,0" Click="Back_Click"/>
|
||||||
<Button x:Name="BackButton" Content="Zurück" Width="110" Margin="0,0,8,0" Click="Back_Click"/>
|
<Button x:Name="NextButton" Content="Weiter" Width="140" Margin="0,0,8,0" Style="{StaticResource AccentButton}" IsDefault="True" Click="Next_Click"/>
|
||||||
<Button x:Name="NextButton" Content="Weiter" Width="140" Margin="0,0,8,0" Style="{StaticResource AccentButton}" IsDefault="True" Click="Next_Click"/>
|
<Button x:Name="CancelButton" Content="Abbrechen" Width="110" Click="Cancel_Click"/>
|
||||||
<Button x:Name="CancelButton" Content="Abbrechen" Width="110" Click="Cancel_Click"/>
|
</StackPanel>
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
<!-- Eigene Rückfrage im Setup-Look statt Windows-Meldungsfenster (siehe Frage()) -->
|
||||||
</Window>
|
<Grid x:Name="DialogOverlay" Grid.RowSpan="3" Grid.ColumnSpan="2" Visibility="Collapsed" Background="#99000000">
|
||||||
|
<Border x:Name="DialogCard" Width="420" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
Background="{DynamicResource LayerBrush}" BorderBrush="{DynamicResource ControlStrokeBrush}" BorderThickness="1"
|
||||||
|
CornerRadius="10" Padding="26,22,22,18" Effect="{StaticResource DialogShadow}" RenderTransformOrigin="0.5,0.5">
|
||||||
|
<Border.RenderTransform><ScaleTransform x:Name="DialogScale" ScaleX="1" ScaleY="1"/></Border.RenderTransform>
|
||||||
|
<StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Border Width="34" Height="34" CornerRadius="17" Background="{DynamicResource AccentSubtleBrush}" VerticalAlignment="Top">
|
||||||
|
<TextBlock x:Name="DialogIcon" Text="" FontFamily="{DynamicResource IconFont}" FontSize="16"
|
||||||
|
Foreground="{DynamicResource AccentBrush}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
<TextBlock x:Name="DialogTitle" FontFamily="{DynamicResource DisplayFont}" FontSize="18" FontWeight="SemiBold"
|
||||||
|
VerticalAlignment="Center" Margin="12,0,0,0" TextWrapping="Wrap" MaxWidth="320"/>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock x:Name="DialogText" TextWrapping="Wrap" LineHeight="20" Margin="46,8,0,0"
|
||||||
|
Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,22,0,0">
|
||||||
|
<Button x:Name="DialogSecondary" MinWidth="120" Margin="0,0,8,0" Click="DialogSecondary_Click"/>
|
||||||
|
<Button x:Name="DialogPrimary" MinWidth="130" Style="{StaticResource AccentButton}" Click="DialogPrimary_Click"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public partial class MainWindow : Window
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_mode = mode;
|
_mode = mode;
|
||||||
VersionText.Text = $"Version {Installer.Version}";
|
VersionText.Text = $"Version {Installer.Version}";
|
||||||
LicenseText.Text = Installer.LicenseText;
|
ZeigeLizenz(Installer.LicenseText);
|
||||||
|
|
||||||
var installed = Installer.InstalledDir;
|
var installed = Installer.InstalledDir;
|
||||||
DirBox.Text = installed ?? Installer.DefaultDir;
|
DirBox.Text = installed ?? Installer.DefaultDir;
|
||||||
@@ -68,6 +68,107 @@ public partial class MainWindow : Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- Lizenztext ----------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LICENSE ist für die Textansicht auf ~80 Zeichen umbrochen und eingerückt. Hier wird das
|
||||||
|
/// wieder zu fließendem Text zusammengesetzt, damit der Umbruch zur Fensterbreite passt:
|
||||||
|
/// Leerzeilen trennen Abschnitte, "1. …" sind Überschriften, "- …" Aufzählungspunkte
|
||||||
|
/// (Folgezeilen gehören zum Punkt), alles andere ist Absatztext.
|
||||||
|
/// </summary>
|
||||||
|
private void ZeigeLizenz(string text)
|
||||||
|
{
|
||||||
|
LicensePanel.Children.Clear();
|
||||||
|
var abschnitte = new List<List<string>>();
|
||||||
|
var aktuell = new List<string>();
|
||||||
|
foreach (var zeile in text.Replace("\r", "").Split('\n'))
|
||||||
|
{
|
||||||
|
if (zeile.Trim().Length == 0)
|
||||||
|
{
|
||||||
|
if (aktuell.Count > 0) { abschnitte.Add(aktuell); aktuell = []; }
|
||||||
|
}
|
||||||
|
else aktuell.Add(zeile);
|
||||||
|
}
|
||||||
|
if (aktuell.Count > 0) abschnitte.Add(aktuell);
|
||||||
|
|
||||||
|
for (int a = 0; a < abschnitte.Count; a++)
|
||||||
|
{
|
||||||
|
var abschnitt = abschnitte[a];
|
||||||
|
int i = 0;
|
||||||
|
if (a == 0)
|
||||||
|
{
|
||||||
|
LicensePanel.Children.Add(LizenzText(abschnitt[0].Trim(), 14, FontWeights.SemiBold, "TextBrush", 0));
|
||||||
|
i = 1;
|
||||||
|
}
|
||||||
|
else if (System.Text.RegularExpressions.Regex.IsMatch(abschnitt[0], @"^\s*\d+\.\s"))
|
||||||
|
{
|
||||||
|
LicensePanel.Children.Add(LizenzText(abschnitt[0].Trim(), 13, FontWeights.SemiBold, "TextBrush", 14));
|
||||||
|
i = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nach einer Überschrift eng anschließen, sonst Abstand zum vorigen Abschnitt
|
||||||
|
double absatzAbstand = i == 1 && a > 0 ? 4 : 10;
|
||||||
|
var absatz = new System.Text.StringBuilder();
|
||||||
|
string? punkt = null;
|
||||||
|
void AbsatzFertig()
|
||||||
|
{
|
||||||
|
if (absatz.Length == 0) return;
|
||||||
|
LicensePanel.Children.Add(LizenzText(absatz.ToString(), 12.5, FontWeights.Normal, "TextSecondaryBrush", absatzAbstand));
|
||||||
|
absatz.Clear();
|
||||||
|
}
|
||||||
|
void PunktFertig()
|
||||||
|
{
|
||||||
|
if (punkt is null) return;
|
||||||
|
LicensePanel.Children.Add(LizenzPunkt(punkt));
|
||||||
|
punkt = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i < abschnitt.Count; i++)
|
||||||
|
{
|
||||||
|
var t = abschnitt[i].Trim();
|
||||||
|
if (t.StartsWith("- "))
|
||||||
|
{
|
||||||
|
AbsatzFertig();
|
||||||
|
PunktFertig();
|
||||||
|
punkt = t.Substring(2).Trim();
|
||||||
|
}
|
||||||
|
else if (punkt is not null) punkt += " " + t;
|
||||||
|
else absatz.Append(absatz.Length > 0 ? " " : "").Append(t);
|
||||||
|
}
|
||||||
|
AbsatzFertig();
|
||||||
|
PunktFertig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextBlock LizenzText(string text, double groesse, FontWeight gewicht, string farbe, double abstandOben)
|
||||||
|
{
|
||||||
|
var tb = new TextBlock
|
||||||
|
{
|
||||||
|
Text = text,
|
||||||
|
TextWrapping = TextWrapping.Wrap,
|
||||||
|
FontSize = groesse,
|
||||||
|
FontWeight = gewicht,
|
||||||
|
LineHeight = groesse + 6,
|
||||||
|
Margin = new Thickness(0, abstandOben, 0, 0),
|
||||||
|
};
|
||||||
|
tb.SetResourceReference(TextBlock.ForegroundProperty, farbe);
|
||||||
|
return tb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aufzählungspunkt mit hängendem Einzug (umbrochene Zeilen beginnen unter dem Text, nicht unter dem Punkt)
|
||||||
|
private Grid LizenzPunkt(string text)
|
||||||
|
{
|
||||||
|
var grid = new Grid { Margin = new Thickness(4, 4, 0, 0) };
|
||||||
|
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(16) });
|
||||||
|
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||||
|
var punkt = LizenzText("•", 12.5, FontWeights.Normal, "AccentBrush", 0);
|
||||||
|
var inhalt = LizenzText(text, 12.5, FontWeights.Normal, "TextSecondaryBrush", 0);
|
||||||
|
Grid.SetColumn(inhalt, 1);
|
||||||
|
grid.Children.Add(punkt);
|
||||||
|
grid.Children.Add(inhalt);
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- Seiten ----------
|
// ---------- Seiten ----------
|
||||||
|
|
||||||
private UIElement? _shownPage;
|
private UIElement? _shownPage;
|
||||||
@@ -182,13 +283,14 @@ public partial class MainWindow : Window
|
|||||||
var dir = DirBox.Text.Trim();
|
var dir = DirBox.Text.Trim();
|
||||||
if (!IsFullPath(dir) || dir.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
|
if (!IsFullPath(dir) || dir.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
|
||||||
{
|
{
|
||||||
MessageBox.Show(this, "Bitte einen gültigen Ordner angeben, z. B. C:\\Spiele\\AeroMC.", "AeroMC Setup");
|
await Frage("Ungültiger Ordner", "Bitte einen vollständigen Ordner angeben, z. B. C:\\Spiele\\AeroMC.", "OK");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (dir.TrimEnd('\\', '/').Length > Installer.MaxDirLength)
|
if (dir.TrimEnd('\\', '/').Length > Installer.MaxDirLength)
|
||||||
{
|
{
|
||||||
MessageBox.Show(this, $"Der Ordnerpfad ist zu lang ({dir.TrimEnd('\\', '/').Length} Zeichen). Windows erlaubt hier höchstens " +
|
await Frage("Ordnerpfad zu lang",
|
||||||
$"{Installer.MaxDirLength} Zeichen – bitte einen kürzeren Ordner wählen, z. B. C:\\Spiele\\AeroMC.", "AeroMC Setup");
|
$"Der Pfad hat {dir.TrimEnd('\\', '/').Length} Zeichen, Windows erlaubt hier höchstens {Installer.MaxDirLength}. " +
|
||||||
|
"Bitte einen kürzeren Ordner wählen, z. B. C:\\Spiele\\AeroMC.", "OK");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (HasImport) ShowPage(PageNumImport);
|
if (HasImport) ShowPage(PageNumImport);
|
||||||
@@ -423,18 +525,87 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
private void Minimize_Click(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized;
|
private void Minimize_Click(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized;
|
||||||
|
|
||||||
private void Cancel_Click(object sender, RoutedEventArgs e)
|
private async void Cancel_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_busy) return;
|
if (_busy || _dialog is not null) return;
|
||||||
if (!_done && _mode == SetupMode.Install && _page > 0 &&
|
if (!_done && _mode == SetupMode.Install && _page > 0)
|
||||||
MessageBox.Show(this, "Installation wirklich abbrechen?", "AeroMC Setup", MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
|
{
|
||||||
return;
|
bool abbrechen = await Frage("Installation abbrechen?",
|
||||||
|
"AeroMC wird nicht installiert. Du kannst das Setup später jederzeit erneut starten.",
|
||||||
|
"Ja, abbrechen", "Weiter installieren", "\uE897");
|
||||||
|
if (!abbrechen) return;
|
||||||
|
}
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- eigene Rückfragen im Setup-Look ----------
|
||||||
|
|
||||||
|
private TaskCompletionSource<bool>? _dialog;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Zeigt eine Rückfrage über dem Fenster. Mit <paramref name="zurueck"/>: zwei Knöpfe –
|
||||||
|
/// der hervorgehobene ist der sichere Weg zurück (liefert false), der andere bestätigt (true).
|
||||||
|
/// Ohne: nur ein Hinweis mit einem Knopf (liefert true).
|
||||||
|
/// </summary>
|
||||||
|
private Task<bool> Frage(string titel, string text, string bestaetigen, string? zurueck = null, string symbol = "\uE7BA")
|
||||||
|
{
|
||||||
|
_dialog?.TrySetResult(false);
|
||||||
|
_dialog = new TaskCompletionSource<bool>();
|
||||||
|
DialogTitle.Text = titel;
|
||||||
|
DialogText.Text = text;
|
||||||
|
DialogIcon.Text = symbol;
|
||||||
|
if (zurueck is null)
|
||||||
|
{
|
||||||
|
DialogSecondary.Visibility = Visibility.Collapsed;
|
||||||
|
DialogPrimary.Content = bestaetigen;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DialogSecondary.Visibility = Visibility.Visible;
|
||||||
|
DialogSecondary.Content = bestaetigen;
|
||||||
|
DialogPrimary.Content = zurueck;
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogOverlay.Visibility = Visibility.Visible;
|
||||||
|
var ein = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(160));
|
||||||
|
DialogOverlay.BeginAnimation(OpacityProperty, ein);
|
||||||
|
var groesse = new DoubleAnimation(0.94, 1, TimeSpan.FromMilliseconds(220)) { EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut } };
|
||||||
|
DialogScale.BeginAnimation(ScaleTransform.ScaleXProperty, groesse);
|
||||||
|
DialogScale.BeginAnimation(ScaleTransform.ScaleYProperty, groesse);
|
||||||
|
DialogPrimary.Focus();
|
||||||
|
return _dialog.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SchliesseFrage(bool ergebnis)
|
||||||
|
{
|
||||||
|
if (_dialog is null) return;
|
||||||
|
var tcs = _dialog;
|
||||||
|
_dialog = null;
|
||||||
|
DialogOverlay.Visibility = Visibility.Collapsed;
|
||||||
|
tcs.TrySetResult(ergebnis);
|
||||||
|
}
|
||||||
|
|
||||||
|
// hervorgehobener Knopf: bei Rückfragen "zurück" (false), beim reinen Hinweis "OK" (true)
|
||||||
|
private void DialogPrimary_Click(object sender, RoutedEventArgs e) =>
|
||||||
|
SchliesseFrage(DialogSecondary.Visibility != Visibility.Visible);
|
||||||
|
|
||||||
|
private void DialogSecondary_Click(object sender, RoutedEventArgs e) => SchliesseFrage(true);
|
||||||
|
|
||||||
|
protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e)
|
||||||
|
{
|
||||||
|
// solange die Rückfrage offen ist: Enter = hervorgehobener Knopf, Esc = zurück
|
||||||
|
if (_dialog is not null)
|
||||||
|
{
|
||||||
|
if (e.Key == System.Windows.Input.Key.Escape) { SchliesseFrage(DialogSecondary.Visibility != Visibility.Visible); e.Handled = true; return; }
|
||||||
|
if (e.Key == System.Windows.Input.Key.Enter) { DialogPrimary_Click(this, new RoutedEventArgs()); e.Handled = true; return; }
|
||||||
|
}
|
||||||
|
base.OnPreviewKeyDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
|
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
|
||||||
{
|
{
|
||||||
if (_busy) e.Cancel = true; // während der Installation nicht schließen
|
if (_busy) e.Cancel = true; // während der Installation nicht schließen
|
||||||
|
_dialog?.TrySetResult(false);
|
||||||
base.OnClosing(e);
|
base.OnClosing(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
},
|
},
|
||||||
"projects": {
|
"projects": {
|
||||||
"C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj": {
|
"C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.5",
|
||||||
"restore": {
|
"restore": {
|
||||||
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj",
|
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj",
|
||||||
"projectName": "AeroMC-Setup",
|
"projectName": "AeroMC-Setup",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -14,11 +14,11 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
|
||||||
[assembly: System.Reflection.AssemblyCopyrightAttribute("Copyright (c) M_Viper. Alle Rechte vorbehalten.")]
|
[assembly: System.Reflection.AssemblyCopyrightAttribute("Copyright (c) M_Viper. Alle Rechte vorbehalten.")]
|
||||||
[assembly: System.Reflection.AssemblyDescriptionAttribute("Installiert AeroMC")]
|
[assembly: System.Reflection.AssemblyDescriptionAttribute("Installiert AeroMC")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.1.4.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.1.5.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.1.4")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.1.5")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("AeroMC Setup")]
|
[assembly: System.Reflection.AssemblyProductAttribute("AeroMC Setup")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("AeroMC Setup")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("AeroMC Setup")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.1.4.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.1.5.0")]
|
||||||
[assembly: System.Resources.NeutralResourcesLanguageAttribute("de-DE")]
|
[assembly: System.Resources.NeutralResourcesLanguageAttribute("de-DE")]
|
||||||
|
|
||||||
// Von der MSBuild WriteCodeFragment-Klasse generiert.
|
// Von der MSBuild WriteCodeFragment-Klasse generiert.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
bf2163e2bc9cc6ae9fd7bde3e12e19f0edbf97f159f3e572c7d202d23603f380
|
6fa3eb63bf9e7c9a77cca67d608dff49330e3a93e70e18548b24192d4ef82422
|
||||||
|
|||||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
|||||||
#pragma checksum "..\..\..\MainWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "880600C0B188AB084F9AD1662CDB9BED2AA222A8"
|
#pragma checksum "..\..\..\MainWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "8CB5D6EC1BE0A9A19A04D9A92B2B0961370843E0"
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <auto-generated>
|
// <auto-generated>
|
||||||
// This code was generated by a tool.
|
// This code was generated by a tool.
|
||||||
@@ -281,9 +281,9 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 270 "..\..\..\MainWindow.xaml"
|
#line 271 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.TextBlock LicenseText;
|
internal System.Windows.Controls.StackPanel LicensePanel;
|
||||||
|
|
||||||
#line default
|
#line default
|
||||||
#line hidden
|
#line hidden
|
||||||
@@ -313,15 +313,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 296 "..\..\..\MainWindow.xaml"
|
#line 301 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
|
||||||
internal System.Windows.Controls.CheckBox StartMenuBox;
|
|
||||||
|
|
||||||
#line default
|
|
||||||
#line hidden
|
|
||||||
|
|
||||||
|
|
||||||
#line 302 "..\..\..\MainWindow.xaml"
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.StackPanel PageImport;
|
internal System.Windows.Controls.StackPanel PageImport;
|
||||||
|
|
||||||
@@ -329,7 +321,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 307 "..\..\..\MainWindow.xaml"
|
#line 306 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.StackPanel ImportList;
|
internal System.Windows.Controls.StackPanel ImportList;
|
||||||
|
|
||||||
@@ -337,7 +329,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 314 "..\..\..\MainWindow.xaml"
|
#line 313 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.StackPanel PageProgress;
|
internal System.Windows.Controls.StackPanel PageProgress;
|
||||||
|
|
||||||
@@ -345,7 +337,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 315 "..\..\..\MainWindow.xaml"
|
#line 314 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.Border SuccessIcon;
|
internal System.Windows.Controls.Border SuccessIcon;
|
||||||
|
|
||||||
@@ -353,7 +345,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 317 "..\..\..\MainWindow.xaml"
|
#line 316 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Media.ScaleTransform SuccessScale;
|
internal System.Windows.Media.ScaleTransform SuccessScale;
|
||||||
|
|
||||||
@@ -361,7 +353,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 322 "..\..\..\MainWindow.xaml"
|
#line 321 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.TextBlock ProgressTitle;
|
internal System.Windows.Controls.TextBlock ProgressTitle;
|
||||||
|
|
||||||
@@ -369,7 +361,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 324 "..\..\..\MainWindow.xaml"
|
#line 323 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.StackPanel ProgressBarPanel;
|
internal System.Windows.Controls.StackPanel ProgressBarPanel;
|
||||||
|
|
||||||
@@ -377,7 +369,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 325 "..\..\..\MainWindow.xaml"
|
#line 324 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.TextBlock PercentText;
|
internal System.Windows.Controls.TextBlock PercentText;
|
||||||
|
|
||||||
@@ -385,7 +377,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 327 "..\..\..\MainWindow.xaml"
|
#line 326 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.Border ProgressTrack;
|
internal System.Windows.Controls.Border ProgressTrack;
|
||||||
|
|
||||||
@@ -393,7 +385,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 328 "..\..\..\MainWindow.xaml"
|
#line 327 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.Border ProgressFill;
|
internal System.Windows.Controls.Border ProgressFill;
|
||||||
|
|
||||||
@@ -401,7 +393,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 331 "..\..\..\MainWindow.xaml"
|
#line 330 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.TextBlock ProgressText;
|
internal System.Windows.Controls.TextBlock ProgressText;
|
||||||
|
|
||||||
@@ -409,7 +401,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 333 "..\..\..\MainWindow.xaml"
|
#line 332 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.StackPanel DonePanel;
|
internal System.Windows.Controls.StackPanel DonePanel;
|
||||||
|
|
||||||
@@ -417,7 +409,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 334 "..\..\..\MainWindow.xaml"
|
#line 333 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.CheckBox LaunchBox;
|
internal System.Windows.Controls.CheckBox LaunchBox;
|
||||||
|
|
||||||
@@ -425,7 +417,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 339 "..\..\..\MainWindow.xaml"
|
#line 338 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.StackPanel PageUninstall;
|
internal System.Windows.Controls.StackPanel PageUninstall;
|
||||||
|
|
||||||
@@ -433,7 +425,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 350 "..\..\..\MainWindow.xaml"
|
#line 349 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.TextBlock DataInfo;
|
internal System.Windows.Controls.TextBlock DataInfo;
|
||||||
|
|
||||||
@@ -441,7 +433,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 351 "..\..\..\MainWindow.xaml"
|
#line 350 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.CheckBox RemoveDataBox;
|
internal System.Windows.Controls.CheckBox RemoveDataBox;
|
||||||
|
|
||||||
@@ -449,7 +441,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 353 "..\..\..\MainWindow.xaml"
|
#line 352 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.TextBlock RemoveDataWarning;
|
internal System.Windows.Controls.TextBlock RemoveDataWarning;
|
||||||
|
|
||||||
@@ -457,7 +449,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 363 "..\..\..\MainWindow.xaml"
|
#line 362 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.Button BackButton;
|
internal System.Windows.Controls.Button BackButton;
|
||||||
|
|
||||||
@@ -465,7 +457,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 364 "..\..\..\MainWindow.xaml"
|
#line 363 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.Button NextButton;
|
internal System.Windows.Controls.Button NextButton;
|
||||||
|
|
||||||
@@ -473,13 +465,77 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
#line 365 "..\..\..\MainWindow.xaml"
|
#line 364 "..\..\..\MainWindow.xaml"
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
internal System.Windows.Controls.Button CancelButton;
|
internal System.Windows.Controls.Button CancelButton;
|
||||||
|
|
||||||
#line default
|
#line default
|
||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
|
#line 368 "..\..\..\MainWindow.xaml"
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
|
internal System.Windows.Controls.Grid DialogOverlay;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
|
#line 369 "..\..\..\MainWindow.xaml"
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
|
internal System.Windows.Controls.Border DialogCard;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
|
#line 372 "..\..\..\MainWindow.xaml"
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
|
internal System.Windows.Media.ScaleTransform DialogScale;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
|
#line 376 "..\..\..\MainWindow.xaml"
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
|
internal System.Windows.Controls.TextBlock DialogIcon;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
|
#line 379 "..\..\..\MainWindow.xaml"
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
|
internal System.Windows.Controls.TextBlock DialogTitle;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
|
#line 382 "..\..\..\MainWindow.xaml"
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
|
internal System.Windows.Controls.TextBlock DialogText;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
|
#line 385 "..\..\..\MainWindow.xaml"
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
|
internal System.Windows.Controls.Button DialogSecondary;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
|
||||||
|
|
||||||
|
#line 386 "..\..\..\MainWindow.xaml"
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||||
|
internal System.Windows.Controls.Button DialogPrimary;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
|
||||||
private bool _contentLoaded;
|
private bool _contentLoaded;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -627,7 +683,7 @@ namespace AeroMCSetup {
|
|||||||
#line hidden
|
#line hidden
|
||||||
return;
|
return;
|
||||||
case 32:
|
case 32:
|
||||||
this.LicenseText = ((System.Windows.Controls.TextBlock)(target));
|
this.LicensePanel = ((System.Windows.Controls.StackPanel)(target));
|
||||||
return;
|
return;
|
||||||
case 33:
|
case 33:
|
||||||
this.PageOptions = ((System.Windows.Controls.StackPanel)(target));
|
this.PageOptions = ((System.Windows.Controls.StackPanel)(target));
|
||||||
@@ -647,101 +703,134 @@ namespace AeroMCSetup {
|
|||||||
this.DesktopLinkBox = ((System.Windows.Controls.CheckBox)(target));
|
this.DesktopLinkBox = ((System.Windows.Controls.CheckBox)(target));
|
||||||
return;
|
return;
|
||||||
case 37:
|
case 37:
|
||||||
this.StartMenuBox = ((System.Windows.Controls.CheckBox)(target));
|
|
||||||
return;
|
|
||||||
case 38:
|
|
||||||
this.PageImport = ((System.Windows.Controls.StackPanel)(target));
|
this.PageImport = ((System.Windows.Controls.StackPanel)(target));
|
||||||
return;
|
return;
|
||||||
case 39:
|
case 38:
|
||||||
this.ImportList = ((System.Windows.Controls.StackPanel)(target));
|
this.ImportList = ((System.Windows.Controls.StackPanel)(target));
|
||||||
return;
|
return;
|
||||||
case 40:
|
case 39:
|
||||||
this.PageProgress = ((System.Windows.Controls.StackPanel)(target));
|
this.PageProgress = ((System.Windows.Controls.StackPanel)(target));
|
||||||
return;
|
return;
|
||||||
case 41:
|
case 40:
|
||||||
this.SuccessIcon = ((System.Windows.Controls.Border)(target));
|
this.SuccessIcon = ((System.Windows.Controls.Border)(target));
|
||||||
return;
|
return;
|
||||||
case 42:
|
case 41:
|
||||||
this.SuccessScale = ((System.Windows.Media.ScaleTransform)(target));
|
this.SuccessScale = ((System.Windows.Media.ScaleTransform)(target));
|
||||||
return;
|
return;
|
||||||
case 43:
|
case 42:
|
||||||
this.ProgressTitle = ((System.Windows.Controls.TextBlock)(target));
|
this.ProgressTitle = ((System.Windows.Controls.TextBlock)(target));
|
||||||
return;
|
return;
|
||||||
case 44:
|
case 43:
|
||||||
this.ProgressBarPanel = ((System.Windows.Controls.StackPanel)(target));
|
this.ProgressBarPanel = ((System.Windows.Controls.StackPanel)(target));
|
||||||
return;
|
return;
|
||||||
case 45:
|
case 44:
|
||||||
this.PercentText = ((System.Windows.Controls.TextBlock)(target));
|
this.PercentText = ((System.Windows.Controls.TextBlock)(target));
|
||||||
return;
|
return;
|
||||||
case 46:
|
case 45:
|
||||||
this.ProgressTrack = ((System.Windows.Controls.Border)(target));
|
this.ProgressTrack = ((System.Windows.Controls.Border)(target));
|
||||||
|
|
||||||
#line 327 "..\..\..\MainWindow.xaml"
|
#line 326 "..\..\..\MainWindow.xaml"
|
||||||
this.ProgressTrack.SizeChanged += new System.Windows.SizeChangedEventHandler(this.ProgressTrack_SizeChanged);
|
this.ProgressTrack.SizeChanged += new System.Windows.SizeChangedEventHandler(this.ProgressTrack_SizeChanged);
|
||||||
|
|
||||||
#line default
|
#line default
|
||||||
#line hidden
|
#line hidden
|
||||||
return;
|
return;
|
||||||
case 47:
|
case 46:
|
||||||
this.ProgressFill = ((System.Windows.Controls.Border)(target));
|
this.ProgressFill = ((System.Windows.Controls.Border)(target));
|
||||||
return;
|
return;
|
||||||
case 48:
|
case 47:
|
||||||
this.ProgressText = ((System.Windows.Controls.TextBlock)(target));
|
this.ProgressText = ((System.Windows.Controls.TextBlock)(target));
|
||||||
return;
|
return;
|
||||||
case 49:
|
case 48:
|
||||||
this.DonePanel = ((System.Windows.Controls.StackPanel)(target));
|
this.DonePanel = ((System.Windows.Controls.StackPanel)(target));
|
||||||
return;
|
return;
|
||||||
case 50:
|
case 49:
|
||||||
this.LaunchBox = ((System.Windows.Controls.CheckBox)(target));
|
this.LaunchBox = ((System.Windows.Controls.CheckBox)(target));
|
||||||
return;
|
return;
|
||||||
case 51:
|
case 50:
|
||||||
this.PageUninstall = ((System.Windows.Controls.StackPanel)(target));
|
this.PageUninstall = ((System.Windows.Controls.StackPanel)(target));
|
||||||
return;
|
return;
|
||||||
case 52:
|
case 51:
|
||||||
this.DataInfo = ((System.Windows.Controls.TextBlock)(target));
|
this.DataInfo = ((System.Windows.Controls.TextBlock)(target));
|
||||||
return;
|
return;
|
||||||
case 53:
|
case 52:
|
||||||
this.RemoveDataBox = ((System.Windows.Controls.CheckBox)(target));
|
this.RemoveDataBox = ((System.Windows.Controls.CheckBox)(target));
|
||||||
|
|
||||||
#line 352 "..\..\..\MainWindow.xaml"
|
#line 351 "..\..\..\MainWindow.xaml"
|
||||||
this.RemoveDataBox.Checked += new System.Windows.RoutedEventHandler(this.RemoveDataBox_Click);
|
this.RemoveDataBox.Checked += new System.Windows.RoutedEventHandler(this.RemoveDataBox_Click);
|
||||||
|
|
||||||
#line default
|
#line default
|
||||||
#line hidden
|
#line hidden
|
||||||
|
|
||||||
#line 352 "..\..\..\MainWindow.xaml"
|
#line 351 "..\..\..\MainWindow.xaml"
|
||||||
this.RemoveDataBox.Unchecked += new System.Windows.RoutedEventHandler(this.RemoveDataBox_Click);
|
this.RemoveDataBox.Unchecked += new System.Windows.RoutedEventHandler(this.RemoveDataBox_Click);
|
||||||
|
|
||||||
#line default
|
#line default
|
||||||
#line hidden
|
#line hidden
|
||||||
return;
|
return;
|
||||||
case 54:
|
case 53:
|
||||||
this.RemoveDataWarning = ((System.Windows.Controls.TextBlock)(target));
|
this.RemoveDataWarning = ((System.Windows.Controls.TextBlock)(target));
|
||||||
return;
|
return;
|
||||||
case 55:
|
case 54:
|
||||||
this.BackButton = ((System.Windows.Controls.Button)(target));
|
this.BackButton = ((System.Windows.Controls.Button)(target));
|
||||||
|
|
||||||
#line 363 "..\..\..\MainWindow.xaml"
|
#line 362 "..\..\..\MainWindow.xaml"
|
||||||
this.BackButton.Click += new System.Windows.RoutedEventHandler(this.Back_Click);
|
this.BackButton.Click += new System.Windows.RoutedEventHandler(this.Back_Click);
|
||||||
|
|
||||||
#line default
|
#line default
|
||||||
#line hidden
|
#line hidden
|
||||||
return;
|
return;
|
||||||
case 56:
|
case 55:
|
||||||
this.NextButton = ((System.Windows.Controls.Button)(target));
|
this.NextButton = ((System.Windows.Controls.Button)(target));
|
||||||
|
|
||||||
#line 364 "..\..\..\MainWindow.xaml"
|
#line 363 "..\..\..\MainWindow.xaml"
|
||||||
this.NextButton.Click += new System.Windows.RoutedEventHandler(this.Next_Click);
|
this.NextButton.Click += new System.Windows.RoutedEventHandler(this.Next_Click);
|
||||||
|
|
||||||
#line default
|
#line default
|
||||||
#line hidden
|
#line hidden
|
||||||
return;
|
return;
|
||||||
case 57:
|
case 56:
|
||||||
this.CancelButton = ((System.Windows.Controls.Button)(target));
|
this.CancelButton = ((System.Windows.Controls.Button)(target));
|
||||||
|
|
||||||
#line 365 "..\..\..\MainWindow.xaml"
|
#line 364 "..\..\..\MainWindow.xaml"
|
||||||
this.CancelButton.Click += new System.Windows.RoutedEventHandler(this.Cancel_Click);
|
this.CancelButton.Click += new System.Windows.RoutedEventHandler(this.Cancel_Click);
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
return;
|
||||||
|
case 57:
|
||||||
|
this.DialogOverlay = ((System.Windows.Controls.Grid)(target));
|
||||||
|
return;
|
||||||
|
case 58:
|
||||||
|
this.DialogCard = ((System.Windows.Controls.Border)(target));
|
||||||
|
return;
|
||||||
|
case 59:
|
||||||
|
this.DialogScale = ((System.Windows.Media.ScaleTransform)(target));
|
||||||
|
return;
|
||||||
|
case 60:
|
||||||
|
this.DialogIcon = ((System.Windows.Controls.TextBlock)(target));
|
||||||
|
return;
|
||||||
|
case 61:
|
||||||
|
this.DialogTitle = ((System.Windows.Controls.TextBlock)(target));
|
||||||
|
return;
|
||||||
|
case 62:
|
||||||
|
this.DialogText = ((System.Windows.Controls.TextBlock)(target));
|
||||||
|
return;
|
||||||
|
case 63:
|
||||||
|
this.DialogSecondary = ((System.Windows.Controls.Button)(target));
|
||||||
|
|
||||||
|
#line 385 "..\..\..\MainWindow.xaml"
|
||||||
|
this.DialogSecondary.Click += new System.Windows.RoutedEventHandler(this.DialogSecondary_Click);
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
return;
|
||||||
|
case 64:
|
||||||
|
this.DialogPrimary = ((System.Windows.Controls.Button)(target));
|
||||||
|
|
||||||
|
#line 386 "..\..\..\MainWindow.xaml"
|
||||||
|
this.DialogPrimary.Click += new System.Windows.RoutedEventHandler(this.DialogPrimary_Click);
|
||||||
|
|
||||||
#line default
|
#line default
|
||||||
#line hidden
|
#line hidden
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -418,7 +418,7 @@
|
|||||||
"C:\\Users\\Viper\\.nuget\\packages\\": {}
|
"C:\\Users\\Viper\\.nuget\\packages\\": {}
|
||||||
},
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.5",
|
||||||
"restore": {
|
"restore": {
|
||||||
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj",
|
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj",
|
||||||
"projectName": "AeroMC-Setup",
|
"projectName": "AeroMC-Setup",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"dgSpecHash": "LtLdHVMRd2c=",
|
"dgSpecHash": "za8GIOA75zI=",
|
||||||
"success": true,
|
"success": true,
|
||||||
"projectFilePath": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj",
|
"projectFilePath": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj",
|
||||||
"expectedPackageFiles": [
|
"expectedPackageFiles": [
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Plugin Name: AeroMC Freunde
|
* Plugin Name: AeroMC Freunde
|
||||||
* Description: Backend für das Freundesystem des AeroMC Launchers - meldet, wer gerade online/im Spiel ist, und verwaltet Freundschaftsanfragen. Keine eigene Anmeldung: der Launcher meldet sich direkt mit der Minecraft-UUID des jeweiligen Kontos.
|
* Description: Backend für das Freundesystem des AeroMC Launchers - meldet, wer gerade online/im Spiel ist, verwaltet Freundschaftsanfragen, Einladungen, Kurznachrichten, per Code geteilte Instanzen und Fehlerberichte aus dem Absturz-Assistenten. Anmeldung über Mojangs Sitzungsprüfung (wie bei Minecraft-Servern) - ohne eigenes Passwort.
|
||||||
* Version: 1.1.0
|
* Version: 1.4.0
|
||||||
* Author: M_Viper
|
* Author: M_Viper
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -10,10 +10,23 @@ if (!defined('ABSPATH')) {
|
|||||||
exit; // kein direkter Aufruf der Datei
|
exit; // kein direkter Aufruf der Datei
|
||||||
}
|
}
|
||||||
|
|
||||||
define('AEROMC_FRIENDS_VERSION', '1.1.0');
|
define('AEROMC_FRIENDS_VERSION', '1.4.0');
|
||||||
define('AEROMC_FRIENDS_PRESENCE_TABLE', 'aeromc_presence');
|
define('AEROMC_FRIENDS_PRESENCE_TABLE', 'aeromc_presence');
|
||||||
define('AEROMC_FRIENDS_FRIENDS_TABLE', 'aeromc_friends');
|
define('AEROMC_FRIENDS_FRIENDS_TABLE', 'aeromc_friends');
|
||||||
define('AEROMC_FRIENDS_INVITES_TABLE', 'aeromc_invites');
|
define('AEROMC_FRIENDS_INVITES_TABLE', 'aeromc_invites');
|
||||||
|
define('AEROMC_FRIENDS_SESSIONS_TABLE', 'aeromc_sessions');
|
||||||
|
define('AEROMC_FRIENDS_SHARES_TABLE', 'aeromc_shares');
|
||||||
|
define('AEROMC_FRIENDS_MESSAGES_TABLE', 'aeromc_messages');
|
||||||
|
define('AEROMC_FRIENDS_REPORTS_TABLE', 'aeromc_reports');
|
||||||
|
|
||||||
|
// Übergangsmodus: false = Anfragen ohne Anmeldung werden noch angenommen, aber
|
||||||
|
// nur für Spieler, die sich noch NIE über die neue Anmeldung gemeldet haben
|
||||||
|
// (ältere Launcher-Versionen funktionieren weiter). Sobald alle Nutzer
|
||||||
|
// aktualisiert haben, in der wp-config.php auf true setzen:
|
||||||
|
// define('AEROMC_FRIENDS_REQUIRE_AUTH', true);
|
||||||
|
if (!defined('AEROMC_FRIENDS_REQUIRE_AUTH')) {
|
||||||
|
define('AEROMC_FRIENDS_REQUIRE_AUTH', false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Legt die beiden Tabellen an (bzw. bringt sie bei einem Update auf den
|
* Legt die beiden Tabellen an (bzw. bringt sie bei einem Update auf den
|
||||||
@@ -25,6 +38,8 @@ function aeromc_friends_install() {
|
|||||||
$presence_table = $wpdb->prefix . AEROMC_FRIENDS_PRESENCE_TABLE;
|
$presence_table = $wpdb->prefix . AEROMC_FRIENDS_PRESENCE_TABLE;
|
||||||
$friends_table = $wpdb->prefix . AEROMC_FRIENDS_FRIENDS_TABLE;
|
$friends_table = $wpdb->prefix . AEROMC_FRIENDS_FRIENDS_TABLE;
|
||||||
$invites_table = $wpdb->prefix . AEROMC_FRIENDS_INVITES_TABLE;
|
$invites_table = $wpdb->prefix . AEROMC_FRIENDS_INVITES_TABLE;
|
||||||
|
$sessions_table = $wpdb->prefix . AEROMC_FRIENDS_SESSIONS_TABLE;
|
||||||
|
$shares_table = $wpdb->prefix . AEROMC_FRIENDS_SHARES_TABLE;
|
||||||
|
|
||||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||||
|
|
||||||
@@ -67,6 +82,67 @@ function aeromc_friends_install() {
|
|||||||
) $charset_collate;";
|
) $charset_collate;";
|
||||||
dbDelta($sql_invites);
|
dbDelta($sql_invites);
|
||||||
|
|
||||||
|
// Anmelde-Sitzungen: nur der SHA-256 des Tokens wird gespeichert
|
||||||
|
$sql_sessions = "CREATE TABLE $sessions_table (
|
||||||
|
token_hash char(64) NOT NULL,
|
||||||
|
uuid varchar(32) NOT NULL,
|
||||||
|
username varchar(32) NOT NULL,
|
||||||
|
created_at datetime NOT NULL,
|
||||||
|
last_used datetime NOT NULL,
|
||||||
|
PRIMARY KEY (token_hash),
|
||||||
|
KEY uuid (uuid)
|
||||||
|
) $charset_collate;";
|
||||||
|
dbDelta($sql_sessions);
|
||||||
|
|
||||||
|
// per Code geteilte Instanzen (Mod-Liste, keine Dateien)
|
||||||
|
$sql_shares = "CREATE TABLE $shares_table (
|
||||||
|
code varchar(9) NOT NULL,
|
||||||
|
uuid varchar(32) NOT NULL,
|
||||||
|
manifest longtext NOT NULL,
|
||||||
|
created_at datetime NOT NULL,
|
||||||
|
PRIMARY KEY (code),
|
||||||
|
KEY uuid (uuid)
|
||||||
|
) $charset_collate;";
|
||||||
|
dbDelta($sql_shares);
|
||||||
|
|
||||||
|
// Kurznachrichten zwischen Freunden (werden nach einigen Tagen gelöscht)
|
||||||
|
$messages_table = $wpdb->prefix . AEROMC_FRIENDS_MESSAGES_TABLE;
|
||||||
|
$sql_messages = "CREATE TABLE $messages_table (
|
||||||
|
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
from_uuid varchar(32) NOT NULL,
|
||||||
|
to_uuid varchar(32) NOT NULL,
|
||||||
|
text varchar(400) NOT NULL,
|
||||||
|
image_url varchar(255) DEFAULT NULL,
|
||||||
|
created_at datetime NOT NULL,
|
||||||
|
read_at datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
KEY to_uuid (to_uuid),
|
||||||
|
KEY from_uuid (from_uuid)
|
||||||
|
) $charset_collate;";
|
||||||
|
dbDelta($sql_messages);
|
||||||
|
|
||||||
|
// Fehlerberichte aus dem Absturz-Assistenten (Ansicht: Werkzeuge → AeroMC Fehlerberichte)
|
||||||
|
$reports_table = $wpdb->prefix . AEROMC_FRIENDS_REPORTS_TABLE;
|
||||||
|
$sql_reports = "CREATE TABLE $reports_table (
|
||||||
|
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
created_at datetime NOT NULL,
|
||||||
|
uuid varchar(32) DEFAULT NULL,
|
||||||
|
username varchar(32) DEFAULT NULL,
|
||||||
|
launcher_version varchar(32) DEFAULT NULL,
|
||||||
|
mc_version varchar(32) DEFAULT NULL,
|
||||||
|
loader varchar(32) DEFAULT NULL,
|
||||||
|
loader_version varchar(64) DEFAULT NULL,
|
||||||
|
system varchar(128) DEFAULT NULL,
|
||||||
|
funde text,
|
||||||
|
mods longtext,
|
||||||
|
bericht longtext,
|
||||||
|
kommentar text,
|
||||||
|
status varchar(20) NOT NULL DEFAULT 'neu',
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
KEY status (status)
|
||||||
|
) $charset_collate;";
|
||||||
|
dbDelta($sql_reports);
|
||||||
|
|
||||||
update_option('aeromc_friends_db_version', AEROMC_FRIENDS_VERSION);
|
update_option('aeromc_friends_db_version', AEROMC_FRIENDS_VERSION);
|
||||||
}
|
}
|
||||||
register_activation_hook(__FILE__, 'aeromc_friends_install');
|
register_activation_hook(__FILE__, 'aeromc_friends_install');
|
||||||
@@ -81,3 +157,6 @@ add_action('plugins_loaded', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
require_once plugin_dir_path(__FILE__) . 'includes/routes.php';
|
require_once plugin_dir_path(__FILE__) . 'includes/routes.php';
|
||||||
|
if (is_admin()) {
|
||||||
|
require_once plugin_dir_path(__FILE__) . 'includes/admin.php';
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin-Seite "AeroMC Fehlerberichte": Liste, Detailansicht, Löschen.
|
||||||
|
* Nur für Administratoren (manage_options).
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_action('admin_menu', function () {
|
||||||
|
global $wpdb;
|
||||||
|
$table = $wpdb->prefix . AEROMC_FRIENDS_REPORTS_TABLE;
|
||||||
|
$neu = (int) $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE status = 'neu'");
|
||||||
|
$titel = 'AeroMC Fehler' . ($neu ? ' <span class="awaiting-mod">' . $neu . '</span>' : '');
|
||||||
|
add_menu_page('AeroMC Fehlerberichte', $titel, 'manage_options', 'aeromc-reports', 'aeromc_friends_reports_page', 'dashicons-sos', 81);
|
||||||
|
});
|
||||||
|
|
||||||
|
function aeromc_friends_reports_page() {
|
||||||
|
global $wpdb;
|
||||||
|
if (!current_user_can('manage_options')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$table = $wpdb->prefix . AEROMC_FRIENDS_REPORTS_TABLE;
|
||||||
|
$basis = admin_url('admin.php?page=aeromc-reports');
|
||||||
|
|
||||||
|
// Löschen (einzeln oder alle erledigten)
|
||||||
|
if (isset($_POST['aeromc_loeschen']) && check_admin_referer('aeromc_reports')) {
|
||||||
|
$id = (int) $_POST['aeromc_loeschen'];
|
||||||
|
$wpdb->delete($table, ['id' => $id]);
|
||||||
|
echo '<div class="notice notice-success"><p>Bericht #' . esc_html($id) . ' gelöscht.</p></div>';
|
||||||
|
}
|
||||||
|
if (isset($_POST['aeromc_status'], $_POST['aeromc_id']) && check_admin_referer('aeromc_reports')) {
|
||||||
|
$status = in_array($_POST['aeromc_status'], ['neu', 'gelesen', 'erledigt'], true) ? $_POST['aeromc_status'] : 'gelesen';
|
||||||
|
$wpdb->update($table, ['status' => $status], ['id' => (int) $_POST['aeromc_id']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '<div class="wrap"><h1>AeroMC Fehlerberichte</h1>';
|
||||||
|
|
||||||
|
// Detailansicht
|
||||||
|
if (isset($_GET['report'])) {
|
||||||
|
$id = (int) $_GET['report'];
|
||||||
|
$r = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE id = %d", $id));
|
||||||
|
if (!$r) {
|
||||||
|
echo '<p>Bericht nicht gefunden.</p></div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($r->status === 'neu') {
|
||||||
|
$wpdb->update($table, ['status' => 'gelesen'], ['id' => $id]);
|
||||||
|
$r->status = 'gelesen';
|
||||||
|
}
|
||||||
|
echo '<p><a href="' . esc_url($basis) . '">← Zur Liste</a></p>';
|
||||||
|
echo '<h2>Bericht #' . esc_html($r->id) . '</h2>';
|
||||||
|
echo '<table class="widefat striped" style="max-width:900px"><tbody>';
|
||||||
|
$zeile = function ($k, $v) {
|
||||||
|
echo '<tr><th style="width:180px">' . esc_html($k) . '</th><td>' . esc_html($v !== null && $v !== '' ? $v : '–') . '</td></tr>';
|
||||||
|
};
|
||||||
|
$zeile('Eingegangen (UTC)', $r->created_at);
|
||||||
|
$zeile('Spieler', $r->username ? $r->username . ' (' . $r->uuid . ')' : 'anonym');
|
||||||
|
$zeile('Launcher-Version', $r->launcher_version);
|
||||||
|
$zeile('Minecraft', $r->mc_version);
|
||||||
|
$zeile('Loader', trim($r->loader . ' ' . $r->loader_version));
|
||||||
|
$zeile('System', $r->system);
|
||||||
|
$zeile('Status', $r->status);
|
||||||
|
echo '</tbody></table>';
|
||||||
|
|
||||||
|
if ($r->kommentar) {
|
||||||
|
echo '<h3>Kommentar</h3><div style="max-width:900px;padding:10px 14px;background:#fff;border:1px solid #ccd0d4;white-space:pre-wrap">' . esc_html($r->kommentar) . '</div>';
|
||||||
|
}
|
||||||
|
$funde = json_decode((string) $r->funde, true) ?: [];
|
||||||
|
echo '<h3>Erkannte Ursachen (' . count($funde) . ')</h3>';
|
||||||
|
if ($funde) {
|
||||||
|
echo '<ul style="list-style:disc;padding-left:20px">';
|
||||||
|
foreach ($funde as $f) {
|
||||||
|
echo '<li><strong>' . esc_html($f['titel'] ?? '') . '</strong> – ' . esc_html($f['text'] ?? '') . '</li>';
|
||||||
|
}
|
||||||
|
echo '</ul>';
|
||||||
|
} else {
|
||||||
|
echo '<p>Keine – der Assistent konnte die Ursache nicht eindeutig erkennen.</p>';
|
||||||
|
}
|
||||||
|
$mods = json_decode((string) $r->mods, true) ?: [];
|
||||||
|
echo '<h3>Mods (' . count($mods) . ')</h3>';
|
||||||
|
if ($mods) {
|
||||||
|
echo '<table class="widefat striped" style="max-width:900px"><thead><tr><th>Datei</th><th>Name</th><th>Version</th><th>Aktiv</th></tr></thead><tbody>';
|
||||||
|
foreach ($mods as $m) {
|
||||||
|
echo '<tr><td>' . esc_html($m['datei'] ?? '') . '</td><td>' . esc_html($m['name'] ?? '') . '</td><td>'
|
||||||
|
. esc_html($m['version'] ?? '') . '</td><td>' . (!empty($m['aktiv']) ? 'ja' : 'nein') . '</td></tr>';
|
||||||
|
}
|
||||||
|
echo '</tbody></table>';
|
||||||
|
}
|
||||||
|
echo '<h3>Bericht / Protokoll</h3><textarea readonly style="width:100%;max-width:1200px;height:420px;font-family:monospace;font-size:12px">'
|
||||||
|
. esc_textarea($r->bericht) . '</textarea>';
|
||||||
|
|
||||||
|
echo '<form method="post" style="margin-top:14px;display:flex;gap:8px">';
|
||||||
|
wp_nonce_field('aeromc_reports');
|
||||||
|
echo '<input type="hidden" name="aeromc_id" value="' . esc_attr($r->id) . '" />';
|
||||||
|
echo '<button class="button" name="aeromc_status" value="erledigt">Als erledigt markieren</button>';
|
||||||
|
echo '<button class="button" name="aeromc_status" value="neu">Wieder als neu markieren</button>';
|
||||||
|
echo '<button class="button button-link-delete" name="aeromc_loeschen" value="' . esc_attr($r->id) . '" onclick="return confirm(\'Bericht wirklich löschen?\')">Löschen</button>';
|
||||||
|
echo '</form></div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Liste
|
||||||
|
$rows = $wpdb->get_results("SELECT id, created_at, username, launcher_version, mc_version, loader, funde, kommentar, status FROM $table ORDER BY id DESC LIMIT 200");
|
||||||
|
if (!$rows) {
|
||||||
|
echo '<p>Noch keine Fehlerberichte eingegangen.</p></div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
echo '<table class="widefat striped"><thead><tr><th>#</th><th>Eingegangen (UTC)</th><th>Spieler</th><th>Launcher</th><th>Minecraft</th><th>Erkannte Ursache</th><th>Kommentar</th><th>Status</th></tr></thead><tbody>';
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$funde = json_decode((string) $r->funde, true) ?: [];
|
||||||
|
$ursache = $funde ? ($funde[0]['titel'] ?? '') . (count($funde) > 1 ? ' (+' . (count($funde) - 1) . ')' : '') : '–';
|
||||||
|
$fett = $r->status === 'neu' ? 'font-weight:600' : '';
|
||||||
|
echo '<tr style="' . $fett . '"><td><a href="' . esc_url(add_query_arg('report', $r->id, $basis)) . '">#' . esc_html($r->id) . '</a></td>'
|
||||||
|
. '<td>' . esc_html($r->created_at) . '</td>'
|
||||||
|
. '<td>' . esc_html($r->username ?: 'anonym') . '</td>'
|
||||||
|
. '<td>' . esc_html($r->launcher_version) . '</td>'
|
||||||
|
. '<td>' . esc_html(trim($r->mc_version . ' ' . $r->loader)) . '</td>'
|
||||||
|
. '<td>' . esc_html($ursache) . '</td>'
|
||||||
|
. '<td>' . esc_html(mb_strimwidth((string) $r->kommentar, 0, 60, '…')) . '</td>'
|
||||||
|
. '<td>' . esc_html($r->status) . '</td></tr>';
|
||||||
|
}
|
||||||
|
echo '</tbody></table></div>';
|
||||||
|
}
|
||||||
@@ -2,11 +2,15 @@
|
|||||||
/**
|
/**
|
||||||
* REST-Routen unter /wp-json/aeromc/v1/... für das Freundesystem.
|
* REST-Routen unter /wp-json/aeromc/v1/... für das Freundesystem.
|
||||||
*
|
*
|
||||||
* Bewusst OHNE echte Authentifizierung (Absprache mit dem Nutzer): der
|
* Anmeldung wie bei Minecraft-Servern: Der Launcher holt sich eine Aufgabe
|
||||||
* Launcher meldet sich einfach mit der Minecraft-UUID des aktiven Kontos an.
|
* (/auth/challenge), meldet sie mit seinem Minecraft-Zugang bei Mojang an
|
||||||
* Wer eine fremde UUID kennt, könnte sich theoretisch als dieser Spieler
|
* (sessionserver .../join), und das Plugin fragt bei Mojang nach, ob genau
|
||||||
* ausgeben - für den privaten Rahmen dieses Projekts akzeptiert. Eingaben
|
* dieser Spieler das getan hat (hasJoined). Erst dann gibt es ein Sitzungs-
|
||||||
* werden trotzdem serverseitig geprüft (Format von UUID/Name) und jede
|
* Token (Kopfzeile X-AeroMC-Token), das jede Anfrage "als dieser Spieler"
|
||||||
|
* mitbringen muss - damit kann sich niemand mehr mit einer fremden UUID
|
||||||
|
* ausgeben. Übergangsmodus siehe AEROMC_FRIENDS_REQUIRE_AUTH.
|
||||||
|
*
|
||||||
|
* Eingaben werden serverseitig geprüft (Format von UUID/Name) und jede
|
||||||
* Datenbankabfrage läuft ausschließlich über $wpdb->prepare, um SQL-Injection
|
* Datenbankabfrage läuft ausschließlich über $wpdb->prepare, um SQL-Injection
|
||||||
* auszuschließen.
|
* auszuschließen.
|
||||||
*/
|
*/
|
||||||
@@ -27,6 +31,127 @@ define('AEROMC_FRIENDS_INVITE_SECONDS', 600);
|
|||||||
// Frühestens nach so vielen Sekunden darf derselbe Freund erneut eingeladen werden.
|
// Frühestens nach so vielen Sekunden darf derselbe Freund erneut eingeladen werden.
|
||||||
define('AEROMC_FRIENDS_INVITE_MIN_GAP', 30);
|
define('AEROMC_FRIENDS_INVITE_MIN_GAP', 30);
|
||||||
|
|
||||||
|
// Sitzungen verfallen, wenn sie so lange nicht benutzt wurden.
|
||||||
|
define('AEROMC_FRIENDS_SESSION_DAYS', 30);
|
||||||
|
// So lange ist ein Teilen-Code gültig.
|
||||||
|
define('AEROMC_FRIENDS_SHARE_DAYS', 30);
|
||||||
|
// Höchstens so viele geteilte Instanzen pro Spieler und Tag.
|
||||||
|
define('AEROMC_FRIENDS_SHARES_PER_DAY', 30);
|
||||||
|
// Größe einer geteilten Mod-Liste (JSON) in Bytes.
|
||||||
|
define('AEROMC_FRIENDS_SHARE_MAX_BYTES', 262144);
|
||||||
|
// Kurznachrichten: Länge, Aufbewahrung, höchstens so viele pro Minute.
|
||||||
|
define('AEROMC_FRIENDS_MESSAGE_MAX_CHARS', 200);
|
||||||
|
define('AEROMC_FRIENDS_MESSAGE_DAYS', 7);
|
||||||
|
define('AEROMC_FRIENDS_MESSAGES_PER_MINUTE', 10);
|
||||||
|
// Bilder in Nachrichten (geteilte Screenshots): Höchstgröße und Ordner unter wp-content/uploads
|
||||||
|
define('AEROMC_FRIENDS_IMAGE_MAX_BYTES', 2500000);
|
||||||
|
define('AEROMC_FRIENDS_IMAGE_DIR', 'aeromc-bilder');
|
||||||
|
|
||||||
|
/** Speichert ein Bild (JPEG/PNG, base64) unter zufälligem Namen; gibt die URL zurück oder WP_Error. */
|
||||||
|
function aeromc_friends_save_image($base64) {
|
||||||
|
$daten = base64_decode(preg_replace('/^data:image\/\w+;base64,/', '', (string) $base64), true);
|
||||||
|
if ($daten === false || strlen($daten) < 100) {
|
||||||
|
return new WP_Error('aeromc_invalid', 'Ungültiges Bild.', ['status' => 400]);
|
||||||
|
}
|
||||||
|
if (strlen($daten) > AEROMC_FRIENDS_IMAGE_MAX_BYTES) {
|
||||||
|
return new WP_Error('aeromc_too_big', 'Das Bild ist zu groß.', ['status' => 413]);
|
||||||
|
}
|
||||||
|
// nur echte JPEG- oder PNG-Dateien annehmen (Dateikopf prüfen, nicht der Angabe trauen)
|
||||||
|
if (strncmp($daten, "\xFF\xD8\xFF", 3) === 0) {
|
||||||
|
$endung = 'jpg';
|
||||||
|
} elseif (strncmp($daten, "\x89PNG\r\n\x1A\n", 8) === 0) {
|
||||||
|
$endung = 'png';
|
||||||
|
} else {
|
||||||
|
return new WP_Error('aeromc_invalid', 'Nur JPEG- oder PNG-Bilder.', ['status' => 400]);
|
||||||
|
}
|
||||||
|
$uploads = wp_upload_dir();
|
||||||
|
$ordner = trailingslashit($uploads['basedir']) . AEROMC_FRIENDS_IMAGE_DIR;
|
||||||
|
if (!wp_mkdir_p($ordner)) {
|
||||||
|
return new WP_Error('aeromc_error', 'Bild konnte nicht gespeichert werden.', ['status' => 500]);
|
||||||
|
}
|
||||||
|
// Verzeichnisliste verhindern
|
||||||
|
if (!file_exists($ordner . '/index.html')) {
|
||||||
|
file_put_contents($ordner . '/index.html', '');
|
||||||
|
}
|
||||||
|
$name = bin2hex(random_bytes(16)) . '.' . $endung;
|
||||||
|
if (file_put_contents($ordner . '/' . $name, $daten) === false) {
|
||||||
|
return new WP_Error('aeromc_error', 'Bild konnte nicht gespeichert werden.', ['status' => 500]);
|
||||||
|
}
|
||||||
|
return trailingslashit($uploads['baseurl']) . AEROMC_FRIENDS_IMAGE_DIR . '/' . $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Bilddatei zu einer gespeicherten URL löschen (nur aus unserem Ordner). */
|
||||||
|
function aeromc_friends_delete_image($url) {
|
||||||
|
if (!$url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$name = basename((string) $url);
|
||||||
|
if (!preg_match('/^[a-f0-9]{32}\.(jpg|png)$/', $name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$uploads = wp_upload_dir();
|
||||||
|
$datei = trailingslashit($uploads['basedir']) . AEROMC_FRIENDS_IMAGE_DIR . '/' . $name;
|
||||||
|
if (file_exists($datei)) {
|
||||||
|
@unlink($datei);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fehlerberichte: Größe des mitgeschickten Berichts, höchstens so viele pro Stunde und Absender.
|
||||||
|
define('AEROMC_FRIENDS_REPORT_MAX_BYTES', 200000);
|
||||||
|
define('AEROMC_FRIENDS_REPORTS_PER_HOUR', 5);
|
||||||
|
|
||||||
|
function aeromc_friends_messages_table() {
|
||||||
|
global $wpdb;
|
||||||
|
return $wpdb->prefix . AEROMC_FRIENDS_MESSAGES_TABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
function aeromc_friends_reports_table() {
|
||||||
|
global $wpdb;
|
||||||
|
return $wpdb->prefix . AEROMC_FRIENDS_REPORTS_TABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
function aeromc_friends_sessions_table() {
|
||||||
|
global $wpdb;
|
||||||
|
return $wpdb->prefix . AEROMC_FRIENDS_SESSIONS_TABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
function aeromc_friends_shares_table() {
|
||||||
|
global $wpdb;
|
||||||
|
return $wpdb->prefix . AEROMC_FRIENDS_SHARES_TABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft, ob die Anfrage wirklich von diesem Spieler kommt.
|
||||||
|
* Gibt die Sitzung (Objekt mit username) zurück, null im Übergangsmodus
|
||||||
|
* ohne Token, oder WP_Error (401), wenn die Anmeldung fehlt oder ungültig ist.
|
||||||
|
*/
|
||||||
|
function aeromc_friends_check_auth($request, $uuid) {
|
||||||
|
global $wpdb;
|
||||||
|
$table = aeromc_friends_sessions_table();
|
||||||
|
$token = (string) $request->get_header('x-aeromc-token');
|
||||||
|
if ($token !== '') {
|
||||||
|
$hash = hash('sha256', $token);
|
||||||
|
$grenze = gmdate('Y-m-d H:i:s', time() - AEROMC_FRIENDS_SESSION_DAYS * 86400);
|
||||||
|
$sitzung = $wpdb->get_row($wpdb->prepare(
|
||||||
|
"SELECT uuid, username, last_used FROM $table WHERE token_hash = %s AND last_used >= %s", $hash, $grenze
|
||||||
|
));
|
||||||
|
if (!$sitzung || $sitzung->uuid !== $uuid) {
|
||||||
|
return new WP_Error('aeromc_auth', 'Anmeldung ungültig oder abgelaufen.', ['status' => 401]);
|
||||||
|
}
|
||||||
|
// "zuletzt benutzt" höchstens stündlich schreiben
|
||||||
|
if (time() - strtotime($sitzung->last_used) > 3600) {
|
||||||
|
$wpdb->update($table, ['last_used' => current_time('mysql', true)], ['token_hash' => $hash]);
|
||||||
|
}
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
|
// ohne Token: nur im Übergangsmodus und nur, solange dieser Spieler sich
|
||||||
|
// noch nie angemeldet hat - sonst könnte man sich weiter als er ausgeben
|
||||||
|
$schon_angemeldet = $wpdb->get_var($wpdb->prepare("SELECT 1 FROM $table WHERE uuid = %s LIMIT 1", $uuid));
|
||||||
|
if (AEROMC_FRIENDS_REQUIRE_AUTH || $schon_angemeldet) {
|
||||||
|
return new WP_Error('aeromc_auth', 'Anmeldung erforderlich.', ['status' => 401]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function aeromc_friends_invites_table() {
|
function aeromc_friends_invites_table() {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
return $wpdb->prefix . AEROMC_FRIENDS_INVITES_TABLE;
|
return $wpdb->prefix . AEROMC_FRIENDS_INVITES_TABLE;
|
||||||
@@ -80,6 +205,53 @@ function aeromc_friends_valid_username($name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add_action('rest_api_init', function () {
|
add_action('rest_api_init', function () {
|
||||||
|
register_rest_route('aeromc/v1', '/auth/challenge', [
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => 'aeromc_friends_route_auth_challenge',
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
]);
|
||||||
|
register_rest_route('aeromc/v1', '/auth/verify', [
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => 'aeromc_friends_route_auth_verify',
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
]);
|
||||||
|
register_rest_route('aeromc/v1', '/share', [
|
||||||
|
[
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => 'aeromc_friends_route_share_create',
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'methods' => 'GET',
|
||||||
|
'callback' => 'aeromc_friends_route_share_get',
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
register_rest_route('aeromc/v1', '/friends/message', [
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => 'aeromc_friends_route_message_send',
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
]);
|
||||||
|
register_rest_route('aeromc/v1', '/friends/messages', [
|
||||||
|
'methods' => 'GET',
|
||||||
|
'callback' => 'aeromc_friends_route_messages',
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
]);
|
||||||
|
register_rest_route('aeromc/v1', '/friends/messages/unread', [
|
||||||
|
'methods' => 'GET',
|
||||||
|
'callback' => 'aeromc_friends_route_messages_unread',
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
]);
|
||||||
|
register_rest_route('aeromc/v1', '/friends/messages/read', [
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => 'aeromc_friends_route_messages_read',
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
]);
|
||||||
|
register_rest_route('aeromc/v1', '/report', [
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => 'aeromc_friends_route_report',
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
]);
|
||||||
register_rest_route('aeromc/v1', '/presence', [
|
register_rest_route('aeromc/v1', '/presence', [
|
||||||
'methods' => 'POST',
|
'methods' => 'POST',
|
||||||
'callback' => 'aeromc_friends_route_presence',
|
'callback' => 'aeromc_friends_route_presence',
|
||||||
@@ -145,6 +317,13 @@ function aeromc_friends_route_presence($request) {
|
|||||||
if (!$uuid || !aeromc_friends_valid_username($username)) {
|
if (!$uuid || !aeromc_friends_valid_username($username)) {
|
||||||
return new WP_Error('aeromc_invalid', 'Ungültige uuid/username.', ['status' => 400]);
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid/username.', ['status' => 400]);
|
||||||
}
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
|
if ($sitzung) {
|
||||||
|
$username = $sitzung->username; // von Mojang bestätigt
|
||||||
|
}
|
||||||
$instance = sanitize_text_field((string) $request->get_param('instance'));
|
$instance = sanitize_text_field((string) $request->get_param('instance'));
|
||||||
$server = sanitize_text_field((string) $request->get_param('server'));
|
$server = sanitize_text_field((string) $request->get_param('server'));
|
||||||
$version = aeromc_friends_clean_version($request->get_param('version'));
|
$version = aeromc_friends_clean_version($request->get_param('version'));
|
||||||
@@ -182,6 +361,10 @@ function aeromc_friends_route_list($request) {
|
|||||||
if (!$uuid) {
|
if (!$uuid) {
|
||||||
return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]);
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]);
|
||||||
}
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
$friends_table = aeromc_friends_friends_table();
|
$friends_table = aeromc_friends_friends_table();
|
||||||
$presence_table = aeromc_friends_presence_table();
|
$presence_table = aeromc_friends_presence_table();
|
||||||
|
|
||||||
@@ -218,6 +401,10 @@ function aeromc_friends_route_requests($request) {
|
|||||||
if (!$uuid) {
|
if (!$uuid) {
|
||||||
return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]);
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]);
|
||||||
}
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
$friends_table = aeromc_friends_friends_table();
|
$friends_table = aeromc_friends_friends_table();
|
||||||
$presence_table = aeromc_friends_presence_table();
|
$presence_table = aeromc_friends_presence_table();
|
||||||
|
|
||||||
@@ -240,6 +427,10 @@ function aeromc_friends_route_sent($request) {
|
|||||||
if (!$uuid) {
|
if (!$uuid) {
|
||||||
return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]);
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]);
|
||||||
}
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
$friends_table = aeromc_friends_friends_table();
|
$friends_table = aeromc_friends_friends_table();
|
||||||
$presence_table = aeromc_friends_presence_table();
|
$presence_table = aeromc_friends_presence_table();
|
||||||
|
|
||||||
@@ -263,6 +454,10 @@ function aeromc_friends_route_request($request) {
|
|||||||
if (!$uuid || !aeromc_friends_valid_username($targetUsername)) {
|
if (!$uuid || !aeromc_friends_valid_username($targetUsername)) {
|
||||||
return new WP_Error('aeromc_invalid', 'Ungültige uuid/targetUsername.', ['status' => 400]);
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid/targetUsername.', ['status' => 400]);
|
||||||
}
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
$presence_table = aeromc_friends_presence_table();
|
$presence_table = aeromc_friends_presence_table();
|
||||||
$friends_table = aeromc_friends_friends_table();
|
$friends_table = aeromc_friends_friends_table();
|
||||||
|
|
||||||
@@ -303,6 +498,10 @@ function aeromc_friends_route_respond($request) {
|
|||||||
if (!$uuid || !$other) {
|
if (!$uuid || !$other) {
|
||||||
return new WP_Error('aeromc_invalid', 'Ungültige uuid/otherUuid.', ['status' => 400]);
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid/otherUuid.', ['status' => 400]);
|
||||||
}
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
$friends_table = aeromc_friends_friends_table();
|
$friends_table = aeromc_friends_friends_table();
|
||||||
|
|
||||||
if ($accept) {
|
if ($accept) {
|
||||||
@@ -327,6 +526,10 @@ function aeromc_friends_route_cancel($request) {
|
|||||||
if (!$uuid || !$other) {
|
if (!$uuid || !$other) {
|
||||||
return new WP_Error('aeromc_invalid', 'Ungültige uuid/otherUuid.', ['status' => 400]);
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid/otherUuid.', ['status' => 400]);
|
||||||
}
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
$friends_table = aeromc_friends_friends_table();
|
$friends_table = aeromc_friends_friends_table();
|
||||||
$wpdb->query($wpdb->prepare(
|
$wpdb->query($wpdb->prepare(
|
||||||
"DELETE FROM $friends_table WHERE uuid_a = %s AND uuid_b = %s AND status = 'pending'",
|
"DELETE FROM $friends_table WHERE uuid_a = %s AND uuid_b = %s AND status = 'pending'",
|
||||||
@@ -343,6 +546,10 @@ function aeromc_friends_route_remove($request) {
|
|||||||
if (!$uuid || !$other) {
|
if (!$uuid || !$other) {
|
||||||
return new WP_Error('aeromc_invalid', 'Ungültige uuid/otherUuid.', ['status' => 400]);
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid/otherUuid.', ['status' => 400]);
|
||||||
}
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
$friends_table = aeromc_friends_friends_table();
|
$friends_table = aeromc_friends_friends_table();
|
||||||
$wpdb->query($wpdb->prepare(
|
$wpdb->query($wpdb->prepare(
|
||||||
"DELETE FROM $friends_table WHERE ((uuid_a = %s AND uuid_b = %s) OR (uuid_a = %s AND uuid_b = %s)) AND status = 'accepted'",
|
"DELETE FROM $friends_table WHERE ((uuid_a = %s AND uuid_b = %s) OR (uuid_a = %s AND uuid_b = %s)) AND status = 'accepted'",
|
||||||
@@ -361,6 +568,10 @@ function aeromc_friends_route_invite($request) {
|
|||||||
if (!$uuid || !$target || !aeromc_friends_valid_server($server)) {
|
if (!$uuid || !$target || !aeromc_friends_valid_server($server)) {
|
||||||
return new WP_Error('aeromc_invalid', 'Ungültige uuid/targetUuid/server.', ['status' => 400]);
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid/targetUuid/server.', ['status' => 400]);
|
||||||
}
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
if (!aeromc_friends_are_friends($uuid, $target)) {
|
if (!aeromc_friends_are_friends($uuid, $target)) {
|
||||||
return new WP_Error('aeromc_not_friends', 'Ihr seid nicht befreundet.', ['status' => 403]);
|
return new WP_Error('aeromc_not_friends', 'Ihr seid nicht befreundet.', ['status' => 403]);
|
||||||
}
|
}
|
||||||
@@ -395,6 +606,10 @@ function aeromc_friends_route_invites($request) {
|
|||||||
if (!$uuid) {
|
if (!$uuid) {
|
||||||
return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]);
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]);
|
||||||
}
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
$table = aeromc_friends_invites_table();
|
$table = aeromc_friends_invites_table();
|
||||||
$presence_table = aeromc_friends_presence_table();
|
$presence_table = aeromc_friends_presence_table();
|
||||||
|
|
||||||
@@ -430,7 +645,383 @@ function aeromc_friends_route_invite_dismiss($request) {
|
|||||||
if (!$uuid || $id <= 0) {
|
if (!$uuid || $id <= 0) {
|
||||||
return new WP_Error('aeromc_invalid', 'Ungültige uuid/id.', ['status' => 400]);
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid/id.', ['status' => 400]);
|
||||||
}
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
$table = aeromc_friends_invites_table();
|
$table = aeromc_friends_invites_table();
|
||||||
$wpdb->query($wpdb->prepare("DELETE FROM $table WHERE id = %d AND to_uuid = %s", $id, $uuid));
|
$wpdb->query($wpdb->prepare("DELETE FROM $table WHERE id = %d AND to_uuid = %s", $id, $uuid));
|
||||||
return ['ok' => true];
|
return ['ok' => true];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Anmeldung über Mojangs Sitzungsprüfung
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** POST /auth/challenge {uuid, username} - Aufgabe für die Anmeldung bei Mojang */
|
||||||
|
function aeromc_friends_route_auth_challenge($request) {
|
||||||
|
$uuid = aeromc_friends_normalize_uuid($request->get_param('uuid'));
|
||||||
|
$username = $request->get_param('username');
|
||||||
|
if (!$uuid || !aeromc_friends_valid_username($username)) {
|
||||||
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid/username.', ['status' => 400]);
|
||||||
|
}
|
||||||
|
$server_id = bin2hex(random_bytes(20));
|
||||||
|
set_transient('aeromc_ch_' . $uuid, ['sid' => $server_id, 'name' => $username], 120);
|
||||||
|
return ['serverId' => $server_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** POST /auth/verify {uuid} - fragt bei Mojang nach und gibt ein Sitzungs-Token aus */
|
||||||
|
function aeromc_friends_route_auth_verify($request) {
|
||||||
|
global $wpdb;
|
||||||
|
$uuid = aeromc_friends_normalize_uuid($request->get_param('uuid'));
|
||||||
|
if (!$uuid) {
|
||||||
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]);
|
||||||
|
}
|
||||||
|
$aufgabe = get_transient('aeromc_ch_' . $uuid);
|
||||||
|
delete_transient('aeromc_ch_' . $uuid); // nur einmal verwendbar
|
||||||
|
if (!is_array($aufgabe) || empty($aufgabe['sid'])) {
|
||||||
|
return new WP_Error('aeromc_auth', 'Keine oder abgelaufene Anmelde-Aufgabe.', ['status' => 403]);
|
||||||
|
}
|
||||||
|
$url = 'https://sessionserver.mojang.com/session/minecraft/hasJoined?username='
|
||||||
|
. rawurlencode($aufgabe['name']) . '&serverId=' . rawurlencode($aufgabe['sid']);
|
||||||
|
$antwort = wp_remote_get($url, ['timeout' => 10]);
|
||||||
|
if (is_wp_error($antwort) || wp_remote_retrieve_response_code($antwort) !== 200) {
|
||||||
|
return new WP_Error('aeromc_auth', 'Mojang hat die Anmeldung nicht bestätigt.', ['status' => 403]);
|
||||||
|
}
|
||||||
|
$profil = json_decode(wp_remote_retrieve_body($antwort), true);
|
||||||
|
if (!is_array($profil) || aeromc_friends_normalize_uuid($profil['id'] ?? '') !== $uuid
|
||||||
|
|| !aeromc_friends_valid_username($profil['name'] ?? '')) {
|
||||||
|
return new WP_Error('aeromc_auth', 'Mojang hat einen anderen Spieler gemeldet.', ['status' => 403]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = aeromc_friends_sessions_table();
|
||||||
|
// abgelaufene Sitzungen (aller Spieler) wegräumen
|
||||||
|
$grenze = gmdate('Y-m-d H:i:s', time() - AEROMC_FRIENDS_SESSION_DAYS * 86400);
|
||||||
|
$wpdb->query($wpdb->prepare("DELETE FROM $table WHERE last_used < %s", $grenze));
|
||||||
|
|
||||||
|
$token = bin2hex(random_bytes(32));
|
||||||
|
$jetzt = current_time('mysql', true);
|
||||||
|
$wpdb->insert($table, [
|
||||||
|
'token_hash' => hash('sha256', $token),
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'username' => $profil['name'],
|
||||||
|
'created_at' => $jetzt,
|
||||||
|
'last_used' => $jetzt,
|
||||||
|
]);
|
||||||
|
return ['token' => $token, 'username' => $profil['name']];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Instanzen per Code teilen
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Code ohne verwechselbare Zeichen (kein 0/O, 1/I/L), Form XXXX-XXXX. */
|
||||||
|
function aeromc_friends_new_share_code() {
|
||||||
|
$zeichen = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
|
||||||
|
$code = '';
|
||||||
|
for ($i = 0; $i < 8; $i++) {
|
||||||
|
$code .= $zeichen[random_int(0, strlen($zeichen) - 1)];
|
||||||
|
if ($i === 3) {
|
||||||
|
$code .= '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Eingegebenen Code vereinheitlichen ("ab12 cd34" -> "AB12-CD34"), false wenn ungültig. */
|
||||||
|
function aeromc_friends_normalize_share_code($code) {
|
||||||
|
$roh = strtoupper(preg_replace('/[^A-Za-z0-9]/', '', (string) $code));
|
||||||
|
if (strlen($roh) !== 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return substr($roh, 0, 4) . '-' . substr($roh, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** POST /share {uuid, manifest} - Mod-Liste einer Instanz hinterlegen, Code zurück */
|
||||||
|
function aeromc_friends_route_share_create($request) {
|
||||||
|
global $wpdb;
|
||||||
|
$uuid = aeromc_friends_normalize_uuid($request->get_param('uuid'));
|
||||||
|
if (!$uuid) {
|
||||||
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]);
|
||||||
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
|
$manifest = $request->get_param('manifest');
|
||||||
|
if (!is_array($manifest) || empty($manifest['minecraft']) || !isset($manifest['inhalte'])) {
|
||||||
|
return new WP_Error('aeromc_invalid', 'Ungültige Instanz-Beschreibung.', ['status' => 400]);
|
||||||
|
}
|
||||||
|
$json = wp_json_encode($manifest);
|
||||||
|
if (!$json || strlen($json) > AEROMC_FRIENDS_SHARE_MAX_BYTES) {
|
||||||
|
return new WP_Error('aeromc_too_big', 'Die Instanz ist zu groß zum Teilen.', ['status' => 413]);
|
||||||
|
}
|
||||||
|
$table = aeromc_friends_shares_table();
|
||||||
|
|
||||||
|
$seit = gmdate('Y-m-d H:i:s', time() - 86400);
|
||||||
|
$heute = (int) $wpdb->get_var($wpdb->prepare(
|
||||||
|
"SELECT COUNT(*) FROM $table WHERE uuid = %s AND created_at >= %s", $uuid, $seit
|
||||||
|
));
|
||||||
|
if ($heute >= AEROMC_FRIENDS_SHARES_PER_DAY) {
|
||||||
|
return new WP_Error('aeromc_too_fast', 'Heute wurden schon sehr viele Instanzen geteilt – bitte morgen wieder.', ['status' => 429]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// abgelaufene Codes (aller Spieler) wegräumen
|
||||||
|
$grenze = gmdate('Y-m-d H:i:s', time() - AEROMC_FRIENDS_SHARE_DAYS * 86400);
|
||||||
|
$wpdb->query($wpdb->prepare("DELETE FROM $table WHERE created_at < %s", $grenze));
|
||||||
|
|
||||||
|
for ($versuch = 0; $versuch < 5; $versuch++) {
|
||||||
|
$code = aeromc_friends_new_share_code();
|
||||||
|
$ok = $wpdb->insert($table, [
|
||||||
|
'code' => $code,
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'manifest' => $json,
|
||||||
|
'created_at' => current_time('mysql', true),
|
||||||
|
]);
|
||||||
|
if ($ok) {
|
||||||
|
return ['code' => $code, 'days' => AEROMC_FRIENDS_SHARE_DAYS];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new WP_Error('aeromc_error', 'Es konnte kein Code erzeugt werden.', ['status' => 500]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** GET /share?code= - geteilte Instanz abrufen (ohne Anmeldung: wer den Code hat, darf sie sehen) */
|
||||||
|
function aeromc_friends_route_share_get($request) {
|
||||||
|
global $wpdb;
|
||||||
|
$code = aeromc_friends_normalize_share_code($request->get_param('code'));
|
||||||
|
if (!$code) {
|
||||||
|
return new WP_Error('aeromc_invalid', 'Ungültiger Code.', ['status' => 400]);
|
||||||
|
}
|
||||||
|
$table = aeromc_friends_shares_table();
|
||||||
|
$presence_table = aeromc_friends_presence_table();
|
||||||
|
$grenze = gmdate('Y-m-d H:i:s', time() - AEROMC_FRIENDS_SHARE_DAYS * 86400);
|
||||||
|
$row = $wpdb->get_row($wpdb->prepare(
|
||||||
|
"SELECT s.code, s.uuid, s.manifest, s.created_at, p.username
|
||||||
|
FROM $table s LEFT JOIN $presence_table p ON p.uuid = s.uuid
|
||||||
|
WHERE s.code = %s AND s.created_at >= %s",
|
||||||
|
$code, $grenze
|
||||||
|
));
|
||||||
|
if (!$row) {
|
||||||
|
return new WP_Error('aeromc_not_found', 'Diesen Code gibt es nicht oder er ist abgelaufen.', ['status' => 404]);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'code' => $row->code,
|
||||||
|
'fromUuid' => $row->uuid,
|
||||||
|
'fromUsername' => $row->username,
|
||||||
|
'createdAt' => $row->created_at,
|
||||||
|
'manifest' => json_decode($row->manifest, true),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Kurznachrichten zwischen Freunden
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** POST /friends/message {uuid, targetUuid, text} */
|
||||||
|
function aeromc_friends_route_message_send($request) {
|
||||||
|
global $wpdb;
|
||||||
|
$uuid = aeromc_friends_normalize_uuid($request->get_param('uuid'));
|
||||||
|
$target = aeromc_friends_normalize_uuid($request->get_param('targetUuid'));
|
||||||
|
if (!$uuid || !$target) {
|
||||||
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid/targetUuid.', ['status' => 400]);
|
||||||
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
|
$text = trim(sanitize_text_field((string) $request->get_param('text')));
|
||||||
|
$bild = $request->get_param('image');
|
||||||
|
if ($text === '' && !$bild) {
|
||||||
|
return new WP_Error('aeromc_invalid', 'Leere Nachricht.', ['status' => 400]);
|
||||||
|
}
|
||||||
|
if (mb_strlen($text) > AEROMC_FRIENDS_MESSAGE_MAX_CHARS) {
|
||||||
|
$text = mb_substr($text, 0, AEROMC_FRIENDS_MESSAGE_MAX_CHARS);
|
||||||
|
}
|
||||||
|
if (!aeromc_friends_are_friends($uuid, $target)) {
|
||||||
|
return new WP_Error('aeromc_not_friends', 'Ihr seid nicht befreundet.', ['status' => 403]);
|
||||||
|
}
|
||||||
|
$table = aeromc_friends_messages_table();
|
||||||
|
|
||||||
|
$seit = gmdate('Y-m-d H:i:s', time() - 60);
|
||||||
|
$letzte_minute = (int) $wpdb->get_var($wpdb->prepare(
|
||||||
|
"SELECT COUNT(*) FROM $table WHERE from_uuid = %s AND created_at >= %s", $uuid, $seit
|
||||||
|
));
|
||||||
|
if ($letzte_minute >= AEROMC_FRIENDS_MESSAGES_PER_MINUTE) {
|
||||||
|
return new WP_Error('aeromc_too_fast', 'Zu viele Nachrichten – bitte kurz warten.', ['status' => 429]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// alte Nachrichten (aller Spieler) samt ihrer Bilder wegräumen
|
||||||
|
$grenze = gmdate('Y-m-d H:i:s', time() - AEROMC_FRIENDS_MESSAGE_DAYS * 86400);
|
||||||
|
$alte_bilder = $wpdb->get_col($wpdb->prepare(
|
||||||
|
"SELECT image_url FROM $table WHERE created_at < %s AND image_url IS NOT NULL", $grenze
|
||||||
|
));
|
||||||
|
foreach ($alte_bilder as $alt) {
|
||||||
|
aeromc_friends_delete_image($alt);
|
||||||
|
}
|
||||||
|
$wpdb->query($wpdb->prepare("DELETE FROM $table WHERE created_at < %s", $grenze));
|
||||||
|
|
||||||
|
$bild_url = null;
|
||||||
|
if ($bild) {
|
||||||
|
$bild_url = aeromc_friends_save_image($bild);
|
||||||
|
if (is_wp_error($bild_url)) {
|
||||||
|
return $bild_url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$wpdb->insert($table, [
|
||||||
|
'from_uuid' => $uuid,
|
||||||
|
'to_uuid' => $target,
|
||||||
|
'text' => $text,
|
||||||
|
'image_url' => $bild_url,
|
||||||
|
'created_at' => current_time('mysql', true),
|
||||||
|
]);
|
||||||
|
return ['ok' => true, 'id' => (int) $wpdb->insert_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** GET /friends/messages?uuid=&with= - Unterhaltung mit einem Freund (älteste zuerst, höchstens 50) */
|
||||||
|
function aeromc_friends_route_messages($request) {
|
||||||
|
global $wpdb;
|
||||||
|
$uuid = aeromc_friends_normalize_uuid($request->get_param('uuid'));
|
||||||
|
$other = aeromc_friends_normalize_uuid($request->get_param('with'));
|
||||||
|
if (!$uuid || !$other) {
|
||||||
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid/with.', ['status' => 400]);
|
||||||
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
|
$table = aeromc_friends_messages_table();
|
||||||
|
$rows = $wpdb->get_results($wpdb->prepare(
|
||||||
|
"SELECT id, from_uuid, text, image_url, created_at, read_at FROM $table
|
||||||
|
WHERE (from_uuid = %s AND to_uuid = %s) OR (from_uuid = %s AND to_uuid = %s)
|
||||||
|
ORDER BY id DESC LIMIT 50",
|
||||||
|
$uuid, $other, $other, $uuid
|
||||||
|
));
|
||||||
|
return array_reverse(array_map(function ($r) {
|
||||||
|
return [
|
||||||
|
'id' => (int) $r->id,
|
||||||
|
'fromUuid' => $r->from_uuid,
|
||||||
|
'text' => $r->text,
|
||||||
|
'imageUrl' => $r->image_url,
|
||||||
|
'createdAt' => $r->created_at,
|
||||||
|
'read' => $r->read_at !== null,
|
||||||
|
];
|
||||||
|
}, $rows));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** GET /friends/messages/unread?uuid= - ungelesene Nachrichten an mich */
|
||||||
|
function aeromc_friends_route_messages_unread($request) {
|
||||||
|
global $wpdb;
|
||||||
|
$uuid = aeromc_friends_normalize_uuid($request->get_param('uuid'));
|
||||||
|
if (!$uuid) {
|
||||||
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]);
|
||||||
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
|
$table = aeromc_friends_messages_table();
|
||||||
|
$presence_table = aeromc_friends_presence_table();
|
||||||
|
$rows = $wpdb->get_results($wpdb->prepare(
|
||||||
|
"SELECT m.id, m.from_uuid, p.username AS from_username, m.text, m.image_url, m.created_at
|
||||||
|
FROM $table m LEFT JOIN $presence_table p ON p.uuid = m.from_uuid
|
||||||
|
WHERE m.to_uuid = %s AND m.read_at IS NULL
|
||||||
|
ORDER BY m.id ASC LIMIT 100",
|
||||||
|
$uuid
|
||||||
|
));
|
||||||
|
return array_map(function ($r) {
|
||||||
|
return [
|
||||||
|
'id' => (int) $r->id,
|
||||||
|
'fromUuid' => $r->from_uuid,
|
||||||
|
'fromUsername' => $r->from_username,
|
||||||
|
'text' => $r->text,
|
||||||
|
'imageUrl' => $r->image_url,
|
||||||
|
'createdAt' => $r->created_at,
|
||||||
|
];
|
||||||
|
}, $rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** POST /friends/messages/read {uuid, fromUuid} - Nachrichten eines Freundes als gelesen markieren */
|
||||||
|
function aeromc_friends_route_messages_read($request) {
|
||||||
|
global $wpdb;
|
||||||
|
$uuid = aeromc_friends_normalize_uuid($request->get_param('uuid'));
|
||||||
|
$from = aeromc_friends_normalize_uuid($request->get_param('fromUuid'));
|
||||||
|
if (!$uuid || !$from) {
|
||||||
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid/fromUuid.', ['status' => 400]);
|
||||||
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
|
$table = aeromc_friends_messages_table();
|
||||||
|
$wpdb->query($wpdb->prepare(
|
||||||
|
"UPDATE $table SET read_at = %s WHERE to_uuid = %s AND from_uuid = %s AND read_at IS NULL",
|
||||||
|
current_time('mysql', true), $uuid, $from
|
||||||
|
));
|
||||||
|
return ['ok' => true];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Fehlerberichte aus dem Absturz-Assistenten
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Kurzer Text: Steuerzeichen raus, auf Länge kürzen. */
|
||||||
|
function aeromc_friends_kurz($wert, $max) {
|
||||||
|
$text = trim(preg_replace('/[\x00-\x1F\x7F]+/u', ' ', (string) $wert));
|
||||||
|
return mb_substr($text, 0, $max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /report {launcherVersion, mcVersion, loader, loaderVersion, system, funde[], mods[], bericht, kommentar, uuid?}
|
||||||
|
* Ohne Anmeldung möglich (der Nutzer kann anonym senden); mit uuid wird die
|
||||||
|
* Anmeldung geprüft, damit niemand Berichte unter fremdem Namen einreicht.
|
||||||
|
*/
|
||||||
|
function aeromc_friends_route_report($request) {
|
||||||
|
global $wpdb;
|
||||||
|
// Schutz gegen Fluten: pro Absender-Adresse begrenzt
|
||||||
|
$ip = isset($_SERVER['REMOTE_ADDR']) ? (string) $_SERVER['REMOTE_ADDR'] : '';
|
||||||
|
$schluessel = 'aeromc_rep_' . md5($ip);
|
||||||
|
$anzahl = (int) get_transient($schluessel);
|
||||||
|
if ($anzahl >= AEROMC_FRIENDS_REPORTS_PER_HOUR) {
|
||||||
|
return new WP_Error('aeromc_too_fast', 'Zu viele Berichte in kurzer Zeit – bitte später noch einmal.', ['status' => 429]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$uuid = null;
|
||||||
|
$username = null;
|
||||||
|
if ($request->get_param('uuid')) {
|
||||||
|
$uuid = aeromc_friends_normalize_uuid($request->get_param('uuid'));
|
||||||
|
if (!$uuid) {
|
||||||
|
return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]);
|
||||||
|
}
|
||||||
|
$sitzung = aeromc_friends_check_auth($request, $uuid);
|
||||||
|
if (is_wp_error($sitzung)) {
|
||||||
|
return $sitzung;
|
||||||
|
}
|
||||||
|
$username = $sitzung ? $sitzung->username : aeromc_friends_kurz($request->get_param('username'), 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bericht = (string) $request->get_param('bericht');
|
||||||
|
if (strlen($bericht) > AEROMC_FRIENDS_REPORT_MAX_BYTES) {
|
||||||
|
$bericht = substr($bericht, -AEROMC_FRIENDS_REPORT_MAX_BYTES);
|
||||||
|
}
|
||||||
|
$funde = $request->get_param('funde');
|
||||||
|
$mods = $request->get_param('mods');
|
||||||
|
|
||||||
|
$wpdb->insert(aeromc_friends_reports_table(), [
|
||||||
|
'created_at' => current_time('mysql', true),
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'username' => $username ?: null,
|
||||||
|
'launcher_version' => aeromc_friends_kurz($request->get_param('launcherVersion'), 32),
|
||||||
|
'mc_version' => aeromc_friends_kurz($request->get_param('mcVersion'), 32),
|
||||||
|
'loader' => aeromc_friends_kurz($request->get_param('loader'), 32),
|
||||||
|
'loader_version' => aeromc_friends_kurz($request->get_param('loaderVersion'), 64),
|
||||||
|
'system' => aeromc_friends_kurz($request->get_param('system'), 128),
|
||||||
|
'funde' => wp_json_encode(is_array($funde) ? array_slice($funde, 0, 20) : []),
|
||||||
|
'mods' => wp_json_encode(is_array($mods) ? array_slice($mods, 0, 500) : []),
|
||||||
|
'bericht' => $bericht,
|
||||||
|
'kommentar' => mb_substr(sanitize_textarea_field((string) $request->get_param('kommentar')), 0, 2000),
|
||||||
|
'status' => 'neu',
|
||||||
|
]);
|
||||||
|
set_transient($schluessel, $anzahl + 1, HOUR_IN_SECONDS);
|
||||||
|
return ['ok' => true, 'id' => (int) $wpdb->insert_id];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user