feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
CI / Go Backend (push) Canceled after 0s

初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
xyou
2026-08-08 14:07:56 +08:00
parent 9500c4c80a
commit f70b061d1a
1972 changed files with 159760 additions and 6 deletions
@@ -0,0 +1,145 @@
<Grid x:Class="PCL.MyCompItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL"
xmlns:svgIcon="clr-namespace:PCL.Core.UI.Controls.SvgIcon;assembly=PCL.Core"
x:Name="PanBack" Height="64"
RenderTransformOrigin="0.5,0.5" Background="{StaticResource ColorBrushSemiTransparent}"
SnapsToDevicePixels="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="7" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="8" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="20" />
<RowDefinition Height="18" />
<RowDefinition Height="16" />
<RowDefinition Height="1.1*" />
</Grid.RowDefinitions>
<!-- Logo -->
<Border Grid.Column="1" Grid.Row="1" Grid.RowSpan="3" IsHitTestVisible="False" SnapsToDevicePixels="True"
UseLayoutRounding="True"
HorizontalAlignment="Right" VerticalAlignment="Center" Width="50" Height="50">
<Border.Clip>
<RectangleGeometry Rect="0,0,50,50" RadiusX="6" RadiusY="6" />
</Border.Clip>
<local:MyImage x:Name="PathLogo" RenderOptions.BitmapScalingMode="HighQuality"
FallbackSource="pack://application:,,,/images/Icons/NoIcon.png" />
</Border>
<!-- 标题 -->
<StackPanel Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="1" VerticalAlignment="Center" Orientation="Horizontal"
IsHitTestVisible="False" SnapsToDevicePixels="False" UseLayoutRounding="False">
<TextBlock x:Name="LabTitle" TextTrimming="WordEllipsis" FontSize="14"
Foreground="{DynamicResource ColorBrush1}" />
<TextBlock x:Name="LabTitleRaw" TextTrimming="WordEllipsis" FontSize="12"
Foreground="{StaticResource ColorBrushGray1}" Opacity="0.4" VerticalAlignment="Center"
Visibility="Collapsed" />
</StackPanel>
<!-- Tag 与 详情 -->
<StackPanel x:Name="PanTags" Grid.Row="2" Grid.Column="3" Orientation="Horizontal" VerticalAlignment="Center"
Margin="-1,0,1,0" Visibility="Collapsed">
<!--<corelocal:BlurBorder Background="{DynamicResource ColorBrush6}" Padding="3,1" CornerRadius="3" Margin="0,0,4,0" SnapsToDevicePixels="True" UseLayoutRounding="False">
<TextBlock Text="科技" Foreground="{DynamicResource ColorBrush2}" FontSize="11" />
</corelocal:BlurBorder>-->
</StackPanel>
<TextBlock x:Name="LabInfo" Grid.Row="2" Grid.Column="4" VerticalAlignment="Center" Margin="0,0,3,0.5"
TextTrimming="CharacterEllipsis" FontSize="12" Foreground="{StaticResource ColorBrushGray3}"
IsHitTestVisible="True" SnapsToDevicePixels="False" UseLayoutRounding="False"
ToolTipService.InitialShowDelay="100" ToolTipService.BetweenShowDelay="100"
ToolTipService.Placement="Relative" ToolTipService.PlacementRectangle="-16,-10,500,10000">
<TextBlock.ToolTip>
<ToolTip x:Name="ToolTipInfo" />
</TextBlock.ToolTip>
</TextBlock>
<!-- 下边栏 -->
<Grid Grid.Row="3" Grid.Column="3" Grid.ColumnSpan="2" VerticalAlignment="Center"
IsHitTestVisible="False" SnapsToDevicePixels="False" UseLayoutRounding="False">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnVersion1" Width="Auto" />
<ColumnDefinition x:Name="ColumnVersion2" Width="Auto" SharedSizeGroup="CompItemVersion" />
<ColumnDefinition x:Name="ColumnVersion3" Width="0.25*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" SharedSizeGroup="LabDownload" />
<ColumnDefinition Width="0.25*" />
<ColumnDefinition x:Name="ColumnTime1" Width="Auto" />
<ColumnDefinition x:Name="ColumnTime2" Width="Auto" MinWidth="57" SharedSizeGroup="CompItemTime" />
<ColumnDefinition x:Name="ColumnTime3" Width="0.25*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" SharedSizeGroup="CompItemSource" />
<ColumnDefinition Width="0.25*" />
</Grid.ColumnDefinitions>
<svgIcon:SvgIcon x:Name="SvgIconVersion"
Grid.Column="0"
IconBrush="{StaticResource ColorBrushGray3}"
Stretch="Uniform"
Height="12"
Icon="lucide/layout-list" />
<TextBlock x:Name="LabVersion"
Grid.Column="1"
Margin="4,0,2,0"
VerticalAlignment="Center"
Foreground="{StaticResource ColorBrushGray3}"
FontSize="12"
TextTrimming="WordEllipsis" />
<svgIcon:SvgIcon x:Name="SvgIconDownload"
Grid.Column="3"
IconBrush="{StaticResource ColorBrushGray4}"
Stretch="Uniform"
Height="12"
Icon="lucide/download" />
<TextBlock x:Name="LabDownload"
Grid.Column="4"
Margin="5,0,2,0"
VerticalAlignment="Center"
Foreground="{StaticResource ColorBrushGray4}"
FontSize="12" />
<svgIcon:SvgIcon x:Name="SvgIconTime"
Grid.Column="6"
IconBrush="{StaticResource ColorBrushGray4}"
Stretch="Uniform"
Height="12"
Margin="0,0,1,0"
Icon="lucide/arrow-up-from-line" />
<TextBlock x:Name="LabTime"
Grid.Column="7"
Margin="5,0,2,0"
VerticalAlignment="Center"
Foreground="{StaticResource ColorBrushGray4}"
FontSize="12" />
<svgIcon:SvgIcon x:Name="SvgIconSource"
Grid.Column="9"
IconBrush="{StaticResource ColorBrushGray4}"
Stretch="Uniform"
Height="12"
Icon="lucide/globe" />
<TextBlock x:Name="LabSource"
Grid.Column="10"
Margin="5,0,2,0"
VerticalAlignment="Center"
Foreground="{StaticResource ColorBrushGray4}"
FontSize="12" />
</Grid>
<!-- 右侧按钮组 -->
<StackPanel x:Name="PanButtons" Grid.Column="5" Grid.Row="0" Grid.RowSpan="5"
Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,5,0" SnapsToDevicePixels="False" UseLayoutRounding="False" Opacity="0">
<!-- 快速下载按钮 -->
<local:MyIconButton x:Name="BtnDownload" Height="25" Width="25" SvgIcon="lucide/download"
Margin="0,0,4,0" Visibility="Collapsed"
ToolTip="{DynamicResource Download.Comp.QuickDownload.Btn.ToolTip}" />
<!-- 删除收藏按钮 -->
<local:MyIconButton x:Name="BtnDelete" Height="25" Width="25" SvgIcon="lucide/heart" />
</StackPanel>
</Grid>
@@ -0,0 +1,467 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace PCL;
public partial class MyCompItem
{
private string stateLast;
/// <summary>
/// 是否允许交互。目前仅用于 PageDownloadCompDetail 的顶部栏展示:若关闭碰撞检测,则无法展开 Tooltip。
/// </summary>
public bool CanInteraction { get; set; } = true;
public void RefreshColor(object sender, EventArgs e)
{
if (!CanInteraction)
return;
// 判断当前颜色
string stateNew;
int time;
if (IsMouseOver)
{
if (isMouseDown)
{
stateNew = "MouseDown";
time = 120;
}
else
{
stateNew = "MouseOver";
time = 120;
}
}
else
{
stateNew = "Idle";
time = 180;
}
if ((stateLast ?? "") == (stateNew ?? ""))
return;
stateLast = stateNew;
// 触发颜色动画
if (IsLoaded && ModAnimation.AniControlEnabled == 0) // 防止默认属性变更触发动画
{
// 有动画
var ani = new List<ModAnimation.AniData>();
if (IsMouseOver)
{
if (PanButtons is not null && _HasActionButtons)
ani.Add(ModAnimation.AaOpacity(PanButtons, 1d - PanButtons.Opacity, (int)Math.Round(time * 0.35d),
(int)Math.Round(time * 0.15d)));
ani.AddRange(new[]
{
ModAnimation.AaColor(RectBack, Border.BackgroundProperty,
isMouseDown ? "ColorBrush6" : "ColorBrushBg1", time),
ModAnimation.AaOpacity(RectBack, 1d - RectBack.Opacity, time,
ease: new ModAnimation.AniEaseOutFluent())
});
if (isMouseDown)
ani.Add(ModAnimation.AaScaleTransform(RectBack,
0.996d - ((ScaleTransform)RectBack.RenderTransform).ScaleX, (int)Math.Round(time * 1.2d),
ease: new ModAnimation.AniEaseOutFluent()));
else
ani.Add(ModAnimation.AaScaleTransform(RectBack,
1d - ((ScaleTransform)RectBack.RenderTransform).ScaleX, (int)Math.Round(time * 1.2d),
ease: new ModAnimation.AniEaseOutFluent()));
}
else
{
if (PanButtons is not null && _HasActionButtons)
ani.Add(ModAnimation.AaOpacity(PanButtons, -PanButtons.Opacity, (int)Math.Round(time * 0.4d)));
ani.AddRange(new[]
{
ModAnimation.AaOpacity(RectBack, -RectBack.Opacity, time),
ModAnimation.AaColor(RectBack, Border.BackgroundProperty,
isMouseDown ? "ColorBrush6" : "ColorBrush7", time),
ModAnimation.AaScaleTransform(RectBack, 0.996d - ((ScaleTransform)RectBack.RenderTransform).ScaleX,
time, ease: new ModAnimation.AniEaseOutFluent()),
ModAnimation.AaScaleTransform(RectBack, -0.196d, 1, after: true)
});
}
ModAnimation.AniStart(ani, "CompItem Color " + Uuid);
}
else
{
// 无动画
ModAnimation.AniStop("CompItem Color " + Uuid);
if (_RectBack is not null)
RectBack.Opacity = 0d;
if (PanButtons is not null)
PanButtons.Opacity = 0d;
}
}
#region
public int Uuid = ModBase.GetUuid();
// Logo
public string Logo
{
get => PathLogo.Source;
set => PathLogo.Source = value;
}
// 标题
public string Title
{
get => LabTitle.Text;
set
{
if ((LabTitle.Text ?? "") == (value ?? ""))
return;
LabTitle.Text = value;
}
}
// 副标题
public string SubTitle
{
get => LabTitleRaw?.Text ?? "";
set
{
if ((LabTitleRaw.Text ?? "") == (value ?? ""))
return;
LabTitleRaw.Text = value;
LabTitleRaw.Visibility = string.IsNullOrEmpty(value) ? Visibility.Collapsed : Visibility.Visible;
}
}
// 描述
public string Description
{
get => LabInfo.Text;
set
{
if ((LabInfo.Text ?? "") == (value ?? ""))
return;
LabInfo.Text = value;
}
}
public MyCompItem()
{
InitializeComponent();
Click += (sender, e) => MyCompItem_Click((MyCompItem)sender, e);
PreviewMouseLeftButtonUp += Button_MouseUp;
PreviewMouseLeftButtonDown += Button_MouseDown;
MouseLeave += Button_MouseLeave;
PreviewMouseLeftButtonUp += Button_MouseLeave;
MouseEnter += RefreshColor;
MouseLeave += RefreshColor;
MouseLeftButtonDown += RefreshColor;
MouseLeftButtonUp += RefreshColor;
// Handles
LabInfo.MouseEnter += LabInfo_MouseEnter;
BtnDelete.Click += BtnDelete_Click;
BtnDownload.Click += _BtnDownload_Click;
}
// 指向时扩展描述
private void LabInfo_MouseEnter(object sender, MouseEventArgs e)
{
if (IsTextTrimmed(LabInfo))
{
ToolTipInfo.Content = LabInfo.Text;
ToolTipInfo.Width = LabInfo.ActualWidth + 25d;
LabInfo.ToolTip = ToolTipInfo;
}
else
{
LabInfo.ToolTip = null;
}
}
private bool IsTextTrimmed(TextBlock textBlock)
{
var typeface = new Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight,
textBlock.FontStretch);
var formattedText = new FormattedText(textBlock.Text, Thread.CurrentThread.CurrentCulture,
textBlock.FlowDirection, typeface, textBlock.FontSize, textBlock.Foreground, ModBase.dpi);
return formattedText.Width > textBlock.ActualWidth;
}
// Tag
public List<string> Tags
{
set
{
PanTags.Children.Clear();
PanTags.Visibility = value.Any() ? Visibility.Visible : Visibility.Collapsed;
foreach (var tagText in value)
{
var newTag = new Border
{
Background = new SolidColorBrush(Color.FromArgb(17, 0, 0, 0)),
Padding = new Thickness(3d, 1d, 3d, 1d),
CornerRadius = new CornerRadius(3d),
Margin = new Thickness(0d, 0d, 3d, 0d),
SnapsToDevicePixels = true,
UseLayoutRounding = false
};
var tagTextBlock = new TextBlock
{
Text = tagText,
Foreground = new SolidColorBrush(Color.FromRgb(134, 134, 134)),
FontSize = 11d
};
newTag.Child = tagTextBlock;
PanTags.Children.Add(newTag);
}
}
}
// ‘收藏按钮
public bool ShowFavoriteBtn
{
get => BtnDelete.Visibility == Visibility.Visible;
set
{
BtnDelete.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
_UpdatePanButtons();
}
}
// 快速下载按钮
public bool ShowDownloadBtn
{
get => BtnDownload.Visibility == Visibility.Visible;
set
{
BtnDownload.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
_UpdatePanButtons();
}
}
/// <summary>右侧是否存在任意可见的操作按钮(收藏 / 下载),用于决定悬停时是否淡入按钮区。</summary>
private bool _HasActionButtons => ShowFavoriteBtn || ShowDownloadBtn;
private void _UpdatePanButtons()
{
if (PanButtons is null) return;
PanButtons.Visibility = _HasActionButtons ? Visibility.Visible : Visibility.Collapsed;
}
/// <summary>
/// 刷新收藏状态(仅更新图标,不影响 hover 展示逻辑)
/// </summary>
public void RefreshFavoriteStatus()
{
if (Tag is not ModComp.CompProject project) return;
var isFavourite = ModComp.CompFavorites.IsFavourite(project.Id);
BtnDelete.SvgIcon = isFavourite ? "lucide/heart-filled" : "lucide/heart";
}
#endregion
#region
// 触发点击事件
public event ClickEventHandler? Click;
public delegate void ClickEventHandler(object sender, MouseButtonEventArgs e);
private void BtnDelete_Click(object sender, EventArgs e)
{
if (PanButtons.Opacity > 0d && Tag is ModComp.CompProject)
{
var project = (ModComp.CompProject)Tag;
ModComp.CompFavorites.ShowMenu(project, (UIElement)sender, () => RefreshFavoriteStatus());
}
}
private void _BtnDownload_Click(object sender, EventArgs e)
{
if (PanButtons.Opacity > 0d && Tag is ModComp.CompProject project)
ModComp.QuickDownload(project);
}
private void MyCompItem_Click(MyCompItem sender, EventArgs e)
{
// 记录当前展开的卡片标题(#2712)
var titles = new List<string>();
if (ModMain.frmMain.pageCurrent.page == FormMain.PageType.CompDetail)
{
foreach (MyCard Card in ModMain.frmDownloadCompDetail.PanResults.Children)
if (!string.IsNullOrEmpty(Card.Title) && !Card.IsSwapped)
titles.Add(Card.Title);
ModBase.Log("[Comp] 记录当前已展开的卡片:" + string.Join("、", titles));
var additional = ModMain.frmMain.pageCurrent.additional.Value;
ModMain.frmMain.pageCurrent.additional = additional with { ExpandedTitles = titles };
}
// 打开详情页
var targetType = default(ModComp.CompType);
string targetVersion = null;
var targetLoader = ModComp.CompLoaderType.Any;
if (ModMain.frmMain.pageCurrent.page == FormMain.PageType.Download)
{
if (ModMain.frmMain.PageCurrentSub == FormMain.PageSubType.DownloadCompFavorites)
{
targetVersion = "";
targetLoader = ModComp.CompLoaderType.Any;
}
else
{
// 从下载页进入
switch (ModMain.frmMain.PageCurrentSub)
{
case FormMain.PageSubType.DownloadMod:
{
targetType = ModComp.CompType.Mod;
targetVersion = ModMain.frmDownloadMod.Content.loader.input.gameVersion;
targetLoader = ModMain.frmDownloadMod.Content.loader.input.modLoader;
break;
}
case FormMain.PageSubType.DownloadPack:
{
targetType = ModComp.CompType.ModPack;
targetVersion = ModMain.frmDownloadPack.Content.loader.input.gameVersion;
break;
}
case FormMain.PageSubType.DownloadDataPack:
{
targetType = ModComp.CompType.DataPack;
targetVersion = ModMain.frmDownloadDataPack.Content.loader.input.gameVersion;
break;
}
case FormMain.PageSubType.DownloadResourcePack:
{
targetType = ModComp.CompType.ResourcePack;
targetVersion = ModMain.frmDownloadResourcePack.Content.loader.input.gameVersion;
break;
}
case FormMain.PageSubType.DownloadShader:
{
targetType = ModComp.CompType.Shader;
targetVersion = ModMain.frmDownloadShader.Content.loader.input.gameVersion;
break;
}
case FormMain.PageSubType.DownloadWorld:
{
targetType = ModComp.CompType.World;
targetVersion = ModMain.frmDownloadWorld.Content.loader.input.gameVersion;
break;
}
}
}
}
else if (ModMain.frmMain.pageCurrent.page == FormMain.PageType.InstanceSetup)
{
// 从实例设置页进入(查看整合包信息)
targetType = ModComp.CompType.ModPack;
}
else
{
// 从详情页进入(查看前置)
targetType = ModComp.CompType.Any; // 允许任意类别
var additional = ModMain.frmMain.pageCurrent.additional.Value;
targetVersion = additional.TargetVersion;
targetLoader = additional.TargetLoader;
}
ModMain.frmMain.PageChange(new FormMain.PageStackData
{
page = FormMain.PageType.CompDetail,
additional = ((ModComp.CompProject)sender.Tag, new List<string>(), targetVersion, targetLoader, targetType, null)
});
}
// 鼠标点击判定
private bool isMouseDown;
// 触发点击事件
private void Button_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!isMouseDown)
return;
Click?.Invoke(sender, e);
}
private void Button_MouseDown(object sender, MouseButtonEventArgs e)
{
if (!CanInteraction)
return;
// 检查点击位置是否在按钮区域内
var clickPosition = e.GetPosition(this);
var isClickOnButton = false;
if (PanButtons.Visibility == Visibility.Visible)
isClickOnButton = _IsClickOnActionButton(BtnDelete, clickPosition) ||
_IsClickOnActionButton(BtnDownload, clickPosition);
// 如果点击在按钮上,不处理主项目点击事件
if (isClickOnButton) return;
// 如果点击在其他区域,按原逻辑处理
// 也要检查是否点击在LabInfo区域(支持ToolTip点击)
var isClickOnLabInfo = false;
if (LabInfo.Visibility == Visibility.Visible)
{
var labInfoBounds = new Rect(LabInfo.TranslatePoint(new Point(0d, 0d), this), LabInfo.RenderSize);
isClickOnLabInfo = labInfoBounds.Contains(clickPosition);
}
if (IsMouseDirectlyOver || isClickOnLabInfo) isMouseDown = true;
}
private void Button_MouseLeave(object sender, object e)
{
isMouseDown = false;
}
// 判断点击是否落在某个操作按钮(收藏 / 下载)上
private bool _IsClickOnActionButton(FrameworkElement button, Point clickPosition)
{
if (button is null || button.Visibility != Visibility.Visible) return false;
var bounds = new Rect(button.TranslatePoint(new Point(0d, 0d), this), button.RenderSize);
return bounds.Contains(clickPosition);
}
#endregion
#region
private Border _RectBack;
public Border RectBack
{
get
{
if (_RectBack is null)
{
var rect = new Border
{
Name = "RectBack",
CornerRadius = new CornerRadius(3d),
RenderTransform = new ScaleTransform(0.8d, 0.8d),
RenderTransformOrigin = new Point(0.5d, 0.5d),
BorderThickness = new Thickness(ModBase.GetWPFSize(1d)),
SnapsToDevicePixels = true,
IsHitTestVisible = false,
Opacity = 0d
};
rect.SetResourceReference(Border.BackgroundProperty, "ColorBrush7");
rect.SetResourceReference(Border.BorderBrushProperty, "ColorBrush6");
SetColumnSpan(rect, 999);
SetRowSpan(rect, 999);
Children.Insert(0, rect);
_RectBack = rect;
// <!--<corelocal:BlurBorder x:Name = "RectBack" CornerRadius="3" RenderTransformOrigin="0.5,0.5" SnapsToDevicePixels="True"
// IsHitTestVisible = "False" Opacity="0" BorderThickness="1"
// Grid.ColumnSpan = "4" Background="{DynamicResource ColorBrush7}" BorderBrush="{DynamicResource ColorBrush6}"/>-->
}
return _RectBack;
}
}
#endregion
}
@@ -0,0 +1,146 @@
<local:MyScrollViewer x:Class="PCL.PageComp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PCL"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<local:MyScrollViewer.Resources>
<BooleanToVisibilityConverter x:Key="VisibilityConverter" />
</local:MyScrollViewer.Resources>
<StackPanel Orientation="Vertical" Margin="25">
<StackPanel x:Name="PanAlways">
<local:MySearchBox x:Name="PanSearchBox" Margin="0,0,0,15" SearchButtonVisibility="Visible" HintText="{DynamicResource Download.Comp.Search.Hint}" />
<local:MyCard Margin="0,0,0,15">
<Grid Margin="7" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1.0*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1.3*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1.0*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
<RowDefinition Height="8" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<!--来源-->
<TextBlock x:Name="LabSource" VerticalAlignment="Center" Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Left" Text="{DynamicResource Download.Comp.Filter.Source}"
Margin="5,0,5,0" />
<local:MyComboBox x:Name="ComboSearchSource" Grid.Row="0" Grid.Column="1">
<local:MyComboBoxItem Content="{DynamicResource Common.Option.All}" Tag="3" IsSelected="True" />
<local:MyComboBoxItem Content="CurseForge" Tag="1"
Visibility="{Binding SupportCurseForge, Converter={StaticResource VisibilityConverter}}" />
<local:MyComboBoxItem Content="Modrinth" Tag="2"
Visibility="{Binding SupportModrinth, Converter={StaticResource VisibilityConverter}}" />
</local:MyComboBox>
<!--Tags-->
<TextBlock x:Name="LabTag" VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Left"
Text="{DynamicResource Download.Comp.Filter.Tag}" Margin="5,0,5,0" />
<local:MyComboBox x:Name="ComboSearchTag" Grid.Column="3" MaxDropDownHeight="320" />
<!--排序方式-->
<TextBlock x:Name="LabSort" VerticalAlignment="Center" Grid.Row="0" Grid.Column="4"
HorizontalAlignment="Left" Text="{DynamicResource Download.Comp.Filter.Sort}"
Margin="5,0,5,0" />
<local:MyComboBox x:Name="ComboSearchSort" Grid.Row="0" Grid.Column="5" MaxDropDownHeight="320">
<local:MyComboBoxItem Content="{DynamicResource Common.Option.Default}" Tag="1"
IsSelected="True" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Relevance}" Tag="2" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Downloads}" Tag="3" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Follows}" Tag="4" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Latest}" Tag="5" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Sort.Updated}" Tag="6" />
</local:MyComboBox>
<!--版本-->
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left"
Text="{DynamicResource Download.Comp.Filter.Version}" Margin="5,0,5,0"
Grid.Row="2" />
<local:MyComboBox x:Name="TextSearchVersion" Grid.Row="2" Grid.Column="1" IsEditable="True"
MaxDropDownHeight="320">
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Filter.Version.Any}"
IsSelected="True" />
<local:MyComboBoxItem Content="1.21.11" />
<local:MyComboBoxItem Content="1.21.1" />
<local:MyComboBoxItem Content="1.20.6" />
<local:MyComboBoxItem Content="1.20.1" />
<local:MyComboBoxItem Content="1.19.4" />
<local:MyComboBoxItem Content="1.19.2" />
<local:MyComboBoxItem Content="1.18.2" />
<local:MyComboBoxItem Content="1.16.5" />
<local:MyComboBoxItem Content="1.12.2" />
<local:MyComboBoxItem Content="1.10.2" />
<local:MyComboBoxItem Content="1.8.9" />
<local:MyComboBoxItem Content="1.7.10" />
</local:MyComboBox>
<!--加载器-->
<TextBlock x:Name="LabLoader" VerticalAlignment="Center" HorizontalAlignment="Left"
Text="{DynamicResource Download.Comp.Filter.Loader}"
Margin="5,0,5,0" Grid.Row="2" Grid.Column="2" />
<local:MyComboBox x:Name="ComboSearchLoader" Grid.Row="2" Grid.Column="3" MaxDropDownHeight="320">
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Filter.Loader.Any}"
IsSelected="True" Tag="0" />
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.Forge}" Tag="1" />
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.NeoForge}" Tag="6" />
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.Fabric}" Tag="4" />
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.Quilt}" Tag="5" />
</local:MyComboBox>
<local:MyComboBox x:Name="ComboSearchShaderLoader" Visibility="Collapsed" Grid.Row="2"
Grid.Column="3" MaxDropDownHeight="320">
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Filter.Loader.AnyShader}" Tag=""
IsSelected="True" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Filter.Loader.VanillaAvailable}"
Tag="/vanilla"
ToolTip="{DynamicResource Download.Comp.Filter.Loader.VanillaAvailable.ToolTip}" />
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.Iris}" Tag="/iris" />
<local:MyComboBoxItem Content="{DynamicResource Common.Installation.OptiFine}" Tag="/optifine" />
</local:MyComboBox>
<!--按钮-->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Column="6">
<local:MyIconButton x:Name="BtnSearchReset"
SvgIcon="lucide/rotate-ccw"
ToolTip="{DynamicResource Download.Comp.Filter.Reset}" LogoScale="0.9"
Margin="3,0,0,0" />
<local:MyIconButton x:Name="BtnSearchInstallModPack"
SvgIcon="lucide/download"
Visibility="Collapsed"
ToolTip="{DynamicResource Download.Comp.Filter.InstallModpack.ToolTip}"
LogoScale="0.9"
ToolTipService.HorizontalOffset="-55" ToolTipService.VerticalOffset="5" />
</StackPanel>
</Grid>
</local:MyCard>
</StackPanel>
<StackPanel x:Name="PanContent">
<local:MyHint x:Name="HintError" HasBorder="True"
Text="{DynamicResource Download.Comp.Hint.ModrinthUnreachable}"
Theme="Yellow" Margin="0,0,0,15" />
<local:MyCard x:Name="CardProjects" UseAnimation="False">
<StackPanel x:Name="PanProjects" Margin="12" />
</local:MyCard>
<local:MyCard Margin="0,15,0,0" x:Name="CardPages" HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" Height="23" Margin="10,7">
<local:MyIconButton x:Name="BtnPageFirst" IsEnabled="False" Opacity="0.2" Margin="0,0.5,5,0.5"
SvgIcon="lucide/chevrons-left" />
<local:MyIconButton x:Name="BtnPageLeft" IsEnabled="False" Opacity="0.2" Margin="0,0,5,0"
SvgIcon="lucide/chevron-left" />
<TextBlock x:Name="LabPage" Text="1"
Margin="8,0,13,0" FontSize="15" VerticalAlignment="Center"
Foreground="{DynamicResource ColorBrush3}" />
<local:MyIconButton x:Name="BtnPageRight" Margin="0,0,30,0"
SvgIcon="lucide/chevron-right" />
</StackPanel>
</local:MyCard>
</StackPanel>
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="True"
x:Name="PanLoad" UseAnimation="False" Margin="40,50">
<local:MyLoading Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</local:MyCard>
</StackPanel>
</local:MyScrollViewer>
@@ -0,0 +1,389 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
using PCL.Core.App.Localization;
namespace PCL;
[ContentProperty("SearchTags")]
public partial class PageComp
{
/// <summary>
/// 每页展示的结果数量。
/// </summary>
public const int pageSize = 40;
public int page;
public ModComp.CompProjectStorage storage = new();
// 结果 UI 化
private void Load_OnFinish()
{
try
{
ModBase.Log($"[Comp] 开始可视化{TypeNameSpaced}列表,已储藏 {storage.results.Count} 个结果,当前在第 {page + 1} 页");
// 列表项
PanProjects.Children.Clear();
var index = Math.Min(page * pageSize, storage.results.Count - 1);
foreach (var result in storage.results.GetRange(index, Math.Min(storage.results.Count - index, pageSize)))
{
var showQuickDownload = result.Type != ModComp.CompType.ModPack &&
result.Type != ModComp.CompType.DataPack;
PanProjects.Children.Add(result.ToCompItem(loader.input.gameVersion is null,
loader.input.modLoader == ModComp.CompLoaderType.Any &&
(PageType == ModComp.CompType.Mod || PageType == ModComp.CompType.ModPack),
showQuickDownload));
}
// 页码
CardPages.Visibility =
storage.results.Count > 40 || storage.curseForgeOffset < storage.curseForgeTotal ||
storage.modrinthOffset < storage.modrinthTotal
? Visibility.Visible
: Visibility.Collapsed;
LabPage.Text = Lang.Number(page + 1, "N0");
BtnPageFirst.IsEnabled = page > 1;
BtnPageFirst.Opacity = page > 1 ? 1d : 0.2d;
BtnPageLeft.IsEnabled = page > 0;
BtnPageLeft.Opacity = page > 0 ? 1d : 0.2d;
var isRightEnabled = storage.results.Count > pageSize * (page + 1) ||
storage.curseForgeOffset < storage.curseForgeTotal ||
storage.modrinthOffset <
storage.modrinthTotal; // 由于 WPF 的未知 bug,读取到的 IsEnabled 可能是错误的值(#3319
BtnPageRight.IsEnabled = isRightEnabled;
BtnPageRight.Opacity = isRightEnabled ? 1d : 0.2d;
// 错误信息
if (storage.errorMessage is null)
{
HintError.Visibility = Visibility.Collapsed;
}
else
{
HintError.Visibility = Visibility.Visible;
HintError.Text = storage.errorMessage;
}
// 强制返回顶部
ScrollToTop();
}
catch (Exception ex)
{
ModBase.Log(
ex,
$"可视化{TypeNameSpaced}列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Comp.Error.OperationFailed"));
}
}
// 自动重试
private void Load_State(object sender, MyLoading.MyLoadingState state, MyLoading.MyLoadingState oldState)
{
switch (loader.State)
{
case ModBase.LoadState.Failed:
{
var errorMessage = "";
if (loader.Error is not null)
errorMessage = loader.Error.Message;
if (errorMessage.Contains(Lang.Text("Common.Error.InvalidJson")))
{
ModBase.Log($"[Download] 下载的{TypeNameSpaced}列表 json 文件损坏,已自动重试", ModBase.LogLevel.Debug);
((MyPageRight)Parent).PageLoaderRestart();
}
break;
}
}
}
// 切换页码
private void BtnPageFirst_Click(object sender, EventArgs e)
{
ChangePage(0);
}
private void BtnPageLeft_Click(object sender, EventArgs e)
{
ChangePage(page - 1);
}
private void BtnPageRight_Click(object sender, EventArgs e)
{
ChangePage(page + 1);
}
private void ChangePage(int newPage)
{
CardPages.IsEnabled = false;
page = newPage;
ModMain.frmMain.BackToTop();
ModBase.Log($"[Download] {TypeName}:切换到第 {page + 1} 页");
ModBase.RunInThread(() =>
{
Thread.Sleep(100); // 等待向上滚的动画结束
ModBase.RunInUi(() => CardPages.IsEnabled = true);
loader.Start();
});
}
// 安装已有整合包按钮
private void BtnSearchInstallModPack_Click(object sender, EventArgs e)
{
ModModpack.ModpackInstall();
}
/// <summary>
/// 刷新所有已显示项目的收藏状态
/// </summary>
public void RefreshAllFavoriteStatus()
{
try
{
foreach (var item in PanProjects.Children)
if (item is MyCompItem)
((MyCompItem)item).RefreshFavoriteStatus();
}
catch (Exception ex)
{
ModBase.Log(ex, "刷新收藏状态时出错");
}
}
#region
/// <summary>
/// 用于 XAML 快速设置的 Tag 下拉框列表。
/// </summary>
public ItemCollection SearchTags => ComboSearchTag.Items;
public static readonly DependencyProperty SupportCurseForgeProperty =
DependencyProperty.Register("SupportCurseForge", typeof(bool), typeof(PageComp), new PropertyMetadata(true));
public bool SupportCurseForge
{
get => (bool)GetValue(SupportCurseForgeProperty);
set => SetValue(SupportCurseForgeProperty, value);
}
public static readonly DependencyProperty SupportModrinthProperty =
DependencyProperty.Register("SupportModrinth", typeof(bool), typeof(PageComp), new PropertyMetadata(true));
public bool SupportModrinth
{
get => (bool)GetValue(SupportModrinthProperty);
set => SetValue(SupportModrinthProperty, value);
}
/// <summary>
/// 英文前后不含空格的可读资源类型名,例如 "Mod"、"整合包"。
/// </summary>
public string TypeName => ModComp.GetCompTypeName(PageType);
/// <summary>
/// 英文前后含一个空格的可读资源类型名,例如 " Mod "、"整合包"。
/// </summary>
public string TypeNameSpaced => TypeName;
/// <summary>
/// 该页面对应的资源类型。
/// </summary>
public ModComp.CompType PageType
{
get => field;
set
{
if (field == value)
return;
field = value;
BtnSearchInstallModPack.Visibility =
value == ModComp.CompType.ModPack ? Visibility.Visible : Visibility.Collapsed;
loader.name = Lang.Text("Download.Comp.List.Source.ResourceFetch", TypeName);
PanSearchBox.HintText = ModComp.GetCompSearchName(value);
Load.Text = ModComp.GetCompLoadingName(value);
}
} = (ModComp.CompType)(-1);
#endregion
#region
/// <summary>
/// 在切换到页面时,应自动将筛选项设置为与该目标 MC 版本和加载器相同。
/// </summary>
public static McInstance targetVersion;
// 在点击 MyCompItem 时会获取 Loader 的输入,以使资源详情页面可以应用相同的筛选项
public ModLoader.LoaderTask<ModComp.CompProjectRequest, int> loader;
private bool isLoaderInited;
public PageComp()
{
loader = new ModLoader.LoaderTask<ModComp.CompProjectRequest, int>(Lang.Text("Download.Comp.List.Source.ResourceFetch", "XXX"), ModComp.CompProjectsGet,
LoaderInput) { reloadTimeout = 60 * 1000 };
Loaded += PageCompControls_Inited;
IsVisibleChanged += PageComp_IsVisibleChanged;
InitializeComponent();
Load.StateChanged += Load_State;
BtnPageFirst.Click += BtnPageFirst_Click;
BtnPageLeft.Click += BtnPageLeft_Click;
BtnPageRight.Click += BtnPageRight_Click;
PanSearchBox.Search += (_, _) => StartNewSearch();
PanSearchBox.KeyDown += EnterTrigger;
TextSearchVersion.KeyDown += EnterTrigger;
BtnSearchReset.Click += (_, _) => ResetFilter();
BtnSearchInstallModPack.Click += BtnSearchInstallModPack_Click;
}
private void PageCompControls_Inited(object sender, EventArgs e)
{
// 不知道从 Initialized 改成 Loaded 会不会有问题,但用 Initialized 会导致初始的筛选器修改被覆盖回默认值
if (targetVersion is not null)
{
// 设置目标
ResetFilter(); // 重置筛选器
TextSearchVersion.Text = targetVersion.Info.VanillaName;
MyComboBoxItem GetTargetItemByName(string name)
{
foreach (MyComboBoxItem Item in ComboSearchLoader.Items)
if (string.Equals(Item.Content?.ToString(), name, StringComparison.OrdinalIgnoreCase))
return Item;
return (MyComboBoxItem)ComboSearchLoader.Items[0];
}
;
if (targetVersion.Info.HasForge)
ComboSearchLoader.SelectedItem = GetTargetItemByName("Forge");
else if (targetVersion.Info.HasFabric)
ComboSearchLoader.SelectedItem = GetTargetItemByName("Fabric");
else if (targetVersion.Info.HasNeoForge)
ComboSearchLoader.SelectedItem = GetTargetItemByName("NeoForge");
else if (targetVersion.Info.HasQuilt) ComboSearchLoader.SelectedItem = GetTargetItemByName("Quilt");
targetVersion = null;
// 如果已经完成请求,则重新开始
if (isLoaderInited)
StartNewSearch();
ScrollToHome();
}
// 加载器初始化
if (isLoaderInited)
return;
isLoaderInited = true;
((MyPageRight)Parent).PageLoaderInit(Load, PanLoad, PanContent, PanAlways, loader, _ => Load_OnFinish(),
LoaderInput);
// 将最高 Drop 加入筛选
if (ModDownload.AllDrops is not null && ModDownload.AllDrops.Count != 0 && ModDownload.AllDrops.First() > 250)
{
var highestVersion = McInstanceInfo.DropToVersion(ModDownload.AllDrops.First());
if ((((MyComboBoxItem)TextSearchVersion.Items[1]).Content.ToString() ?? "") !=
(highestVersion ?? "")) // 0 是全部
TextSearchVersion.Items.Insert(1, new MyComboBoxItem { Content = highestVersion });
}
// 根据页面类型控制加载器选择的显示
if (PageType == ModComp.CompType.Shader)
{
LabLoader.Visibility = Visibility.Visible;
ComboSearchLoader.Visibility = Visibility.Collapsed;
ComboSearchShaderLoader.Visibility = Visibility.Visible;
}
else if (PageType == ModComp.CompType.Mod || PageType == ModComp.CompType.ModPack)
{
LabLoader.Visibility = Visibility.Visible;
ComboSearchLoader.Visibility = Visibility.Visible;
ComboSearchShaderLoader.Visibility = Visibility.Collapsed;
}
else
{
LabLoader.Visibility = Visibility.Collapsed;
ComboSearchLoader.Visibility = Visibility.Collapsed;
ComboSearchShaderLoader.Visibility = Visibility.Collapsed;
}
}
private void PageComp_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// 当页面变为可见时刷新收藏按钮状态
if (IsVisible) RefreshAllFavoriteStatus();
}
private ModComp.CompProjectRequest LoaderInput()
{
var request = new ModComp.CompProjectRequest(PageType, storage, (page + 1) * pageSize);
var gameVersion = TextSearchVersion.Text == Lang.Text("Download.Comp.Filter.Version.AllInputAvailable") ? null :
TextSearchVersion.Text.Contains(".") || TextSearchVersion.Text.Contains("w") ? TextSearchVersion.Text :
null;
var modLoader = ModComp.CompLoaderType.Any;
if (PageType == ModComp.CompType.Mod || PageType == ModComp.CompType.ModPack) // 只有 Mod 考虑加载器
{
modLoader = (ModComp.CompLoaderType)ModBase.Val(((MyComboBoxItem)ComboSearchLoader.SelectedItem).Tag);
if (gameVersion is not null && gameVersion.Contains(".") && ModBase.Val(gameVersion.Split(".")[1]) < 14d &&
modLoader == ModComp.CompLoaderType.Forge) // 1.14-
// 选择了 Forge
modLoader = ModComp.CompLoaderType.Any; // 此时,视作没有筛选 Mod Loader(因为部分老 Mod 没有设置自己支持的加载器)
}
request.searchText = PanSearchBox.Text;
request.gameVersion = gameVersion;
var selectedTag = (ComboSearchTag.SelectedItem as FrameworkElement)?.Tag?.ToString();
var loaderTag = (ComboSearchShaderLoader.SelectedItem as FrameworkElement)?.Tag?.ToString();
request.tag = PageType == ModComp.CompType.Shader
? string.IsNullOrEmpty(loaderTag)
? selectedTag
: selectedTag + loaderTag
: selectedTag;
request.modLoader =
(ModComp.CompLoaderType)(PageType == ModComp.CompType.Mod || PageType == ModComp.CompType.ModPack
? ModBase.Val(((MyComboBoxItem)ComboSearchLoader.SelectedItem).Tag)
: (double)ModComp.CompLoaderType.Any);
request.source = (ModComp.CompSourceType)ModBase.Val(((MyComboBoxItem)ComboSearchSource.SelectedItem).Tag);
request.sort = (ModComp.CompSortType)ModBase.Val(((MyComboBoxItem)ComboSearchSort.SelectedItem).Tag);
return request;
}
#endregion
#region
// 搜索按钮
private void StartNewSearch()
{
page = 0;
object argInput = LoaderInput();
if (loader.ShouldStart(ref argInput))
storage = new ModComp.CompProjectStorage(); // 避免连续搜索两次使得 CompProjectStorage 引用丢失(#1311
loader.Start();
}
private void EnterTrigger(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
StartNewSearch();
}
// 重置按钮
private void ResetFilter()
{
PanSearchBox.Text = "";
TextSearchVersion.Text = Lang.Text("Download.Comp.Filter.Version.AllInputAvailable");
TextSearchVersion.SelectedIndex = 0;
ComboSearchSource.SelectedIndex = 0;
ComboSearchTag.SelectedIndex = 0;
ComboSearchLoader.SelectedIndex = 0;
ComboSearchShaderLoader.SelectedIndex = 0;
ComboSearchSort.SelectedIndex = 0;
loader.lastFinishedTime = 0L; // 要求强制重新开始
}
private void BtnSearchReset_Click(object sender, EventArgs e)
{
ResetFilter();
}
#endregion
}
@@ -0,0 +1,67 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadCompDetail"
PanScroll="{Binding ElementName=PanBack}">
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
<Grid Margin="25,25,25,10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<local:MyCard Margin="0,0,0,25" x:Name="CardIntro">
<StackPanel Margin="22,20,2,20" x:Name="PanIntro">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<local:MyIconTextButton x:Name="BtnIntroWeb" Text="CurseForge"
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
SvgIcon="lucide/globe"
Margin="0,0,3,0" HorizontalAlignment="Left" />
<local:MyIconTextButton x:Name="BtnIntroWiki"
Text="{DynamicResource Download.Comp.Detail.McMod}"
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
SvgIcon="lucide/globe"
Margin="0,0,3,0" HorizontalAlignment="Left" />
<local:MyIconTextButton x:Name="BtnIntroCopy"
Text="{DynamicResource Download.Comp.Detail.CopyName}"
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
SvgIcon="lucide/copy"
Margin="0,0,3,0" HorizontalAlignment="Left" />
<local:MyIconTextButton x:Name="BtnIntroLinkCopy"
Text="{DynamicResource Download.Comp.Detail.CopyLink}"
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
SvgIcon="lucide/copy"
Margin="0,0,3,0" HorizontalAlignment="Left" />
<local:MyIconTextButton x:Name="BtnTranslate"
Text="{DynamicResource Download.Comp.Detail.TranslateDescription}"
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
SvgIcon="lucide/earth"
Margin="0,0,3,0" HorizontalAlignment="Left" />
<local:MyIconTextButton x:Name="BtnFavorites"
Text="{DynamicResource Download.Comp.Detail.Favorite}"
BorderBrush="{DynamicResource ColorBrush6}" BorderThickness="1"
SvgIcon="lucide/heart"
Margin="0,0,2,0" HorizontalAlignment="Left" />
</StackPanel>
</StackPanel>
</local:MyCard>
<StackPanel Grid.Row="1" Grid.RowSpan="2" Name="PanMain" Grid.IsSharedSizeScope="True">
<local:MyCard Margin="0,0,0,15" x:Name="CardFilter">
<StackPanel>
<StackPanel x:Name="PanInstanceFilter" Margin="10,10,0,5" Orientation="Horizontal"
VerticalAlignment="Top" HorizontalAlignment="Left" />
<StackPanel x:Name="PanModLoaderFilter" Margin="10,5,0,10" Orientation="Horizontal"
VerticalAlignment="Top" HorizontalAlignment="Left" />
</StackPanel>
</local:MyCard>
<StackPanel Name="PanResults" />
</StackPanel>
<local:MyCard Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"
SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="0,0,0,8">
<local:MyLoading Text="{DynamicResource Download.Version.LoadingList}" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</local:MyCard>
</Grid>
</local:MyScrollViewer>
</local:MyPageRight>
@@ -0,0 +1,1183 @@
using System.Collections;
using System.Collections.ObjectModel;
using System.Diagnostics.Eventing.Reader;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using FluentValidation;
using PCL.Core.App;
using PCL.Core.App.Localization;
using PCL.Core.Logging;
using PCL.Core.Minecraft.ResourceProject;
using PCL.Core.UI;
using PCL.Core.Utils;
using PCL.Core.Utils.Validate;
using PCL.Network;
using PCL.Network.Loaders;
using Control = System.Windows.Forms.Control;
namespace PCL;
public partial class PageDownloadCompDetail
{
// 资源下载;整合包另存为
public static Dictionary<ModComp.CompType, string> cachedFolder = new(); // 仅在本次缓存的下载文件夹
private MyCompItem _compItem;
private bool _isFirstInit = true;
private void Init()
{
ModAnimation.AniControlEnabled += 1;
_project = ModMain.frmMain.pageCurrent.additional.Value.CompProject;
PanBack.ScrollToHome();
// 重启加载器
if (_isFirstInit)
// 在 Me.Initialized 已经初始化了加载器,不再重复初始化
_isFirstInit = false;
else
PageLoaderRestart(isForceRestart: true);
// 放置当前工程
if (_compItem is not null)
PanIntro.Children.Remove(_compItem);
_compItem = _project.ToCompItem(true, true);
_compItem.CanInteraction = false;
_compItem.ShowFavoriteBtn = false;
_compItem.Margin = new Thickness(-7, -7, 0d, 8d);
PanIntro.Children.Insert(0, _compItem);
// 决定按钮显示
BtnIntroWeb.Text = _project.FromCurseForge ? "CurseForge" : "Modrinth";
BtnIntroWiki.Visibility = Lang.IsChineseMainland && _project.WikiId != 0
? Visibility.Visible
: Visibility.Collapsed;
BtnTranslate.Visibility = Lang.IsChineseMainland
? Visibility.Visible
: Visibility.Collapsed;
RefreshFavoriteButton();
ModAnimation.AniControlEnabled -= 1;
}
// 整合包安装
public void Install_Click(MyListItem sender, EventArgs e)
{
try
{
// 获取基本信息
var file = (ModComp.CompFile)sender.Tag;
var loaderName =
$"{(_project.FromCurseForge ? "CurseForge" : "Modrinth")} {Lang.Text("Download.Comp.Detail.ModpackDownload")}{_project.TranslatedName} ";
// 获取实例名
var packName = _project.TranslatedName.Replace(".zip", "").Replace(".rar", "").Replace(".mrpack", "")
.Replace(@"\", "").Replace("/", "").Replace("|", "").Replace(":", "").Replace("<", "")
.Replace(">", "").Replace("*", "").Replace("?", "").Replace("\"", "").Replace(" ", "");
var validate = new FolderNameValidator(ModFolder.mcFolderSelected + "versions");
if (!validate.Validate(packName).IsValid)
packName = "";
var instanceName = ModMain.MyMsgBoxInput(Lang.Text("Download.Comp.Detail.InputInstanceName"), "", packName, [validate]);
if (string.IsNullOrEmpty(instanceName))
return;
// 构造步骤加载器
var loaders = new List<ModLoader.LoaderBase>();
var target =
$@"{ModFolder.mcFolderSelected}versions\{instanceName}\原始整合包.{(_project.FromCurseForge ? "zip" : "mrpack")}";
var logoFileAddress = MyImage.GetTempPath(_compItem.Logo);
loaders.Add(new LoaderDownload(Lang.Text("Download.Comp.Detail.DownloadModpackFile"),
new List<DownloadFile> { file.ToNetFile(target, ModComp.DownloadReason.ModPack) })
{ ProgressWeight = 10d, block = true });
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Download.Comp.Detail.PrepareModpackInstall"),
_ => ModModpack.ModpackInstall(target, instanceName,
System.IO.File.Exists(logoFileAddress) ? logoFileAddress : null, file.ProjectId,
true)) { ProgressWeight = 0.1d });
// 启动
var loader = new ModLoader.LoaderCombo<string>(loaderName, loaders)
{
OnStateChanged = myLoader =>
{
switch (myLoader.State)
{
case ModBase.LoadState.Failed:
{
HintService.Hint(
Lang.Text("Download.Comp.Detail.Task.Failed", myLoader.name,
myLoader.Error.ToString()), HintType.Error);
break;
}
case ModBase.LoadState.Aborted:
{
HintService.Hint(Lang.Text("Download.Comp.Detail.Task.Cancelled", myLoader.name));
break;
}
case ModBase.LoadState.Loading:
{
return; // 不重新加载版本列表
}
}
ModDownloadLib.McInstallFailedClearFolder(myLoader);
}
};
loader.Start(Path.Combine(ModFolder.mcFolderSelected, "versions", instanceName));
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"下载资源整合包失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Comp.Error.OperationFailed"));
}
}
// 世界下载
public void InstallWorld_Click(MyListItem sender, EventArgs e)
{
try
{
// 获取基本信息
var file = (ModComp.CompFile)sender.Tag;
var loaderName = $"{(_project.FromCurseForge ? "CurseForge" : "Modrinth")} {Lang.Text("Download.Comp.Detail.WorldDownload")}{_project.TranslatedName} ";
// 确认默认保存位置
string defaultFolder = null;
var subFolder = @"saves\";
Func<McInstance, bool> isVersionSuitable = null;
// 获取资源所需的加载器
var allowedLoaders = new List<ModComp.CompLoaderType>();
if (file.ModLoaders.Any())
allowedLoaders = file.ModLoaders;
else if (_project.ModLoaders.Any()) allowedLoaders = _project.ModLoaders;
ModBase.Log("[Comp] 世界要求的加载器种类:" + (allowedLoaders.Any() ? allowedLoaders.Join(" / ") : "无要求"));
// 判断某个版本是否符合资源要求
isVersionSuitable = version =>
{
if (version is null)
return false;
if (!version.IsLoaded)
version.Load();
if (file.GameVersions.Any(v => v.Contains(".")) && !file.GameVersions.Any(v =>
v.Contains(".") && (v ?? "") == (version.Info.VanillaName ?? "")))
return false;
// 加载器
if (!allowedLoaders.Any())
return true; // 无要求
return false;
};
// 获取常规资源默认下载位置
if (cachedFolder.ContainsKey(file.Type) && !string.IsNullOrEmpty(cachedFolder[file.Type]))
{
defaultFolder = cachedFolder.GetOrDefault(file.Type,
ModInstanceList.McMcInstanceSelected?.PathIndie ?? ModBase.exePath);
ModBase.Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{defaultFolder}");
}
else if (ModInstanceList.McMcInstanceSelected is not null && isVersionSuitable(ModInstanceList.McMcInstanceSelected))
{
defaultFolder = $"{ModInstanceList.McMcInstanceSelected.PathIndie}{subFolder}";
Directory.CreateDirectory(defaultFolder);
ModBase.Log($"[Comp] 使用当前实例作为默认下载位置:{defaultFolder}");
}
else
{
// 查找所有可能的实例
var needLoad = ModInstanceList.mcInstanceListLoader.State != ModBase.LoadState.Finished;
if (needLoad)
{
HintService.Hint(Lang.Text("Download.Comp.Detail.FindingApplicableInstance"));
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
ModLoader.LoaderFolderRunType.ForceRun, 1, @"versions\", true);
}
var suitableVersions = ModInstanceList.mcInstanceList.Values.SelectMany(l => l)
.Where(v => isVersionSuitable(v)).Select(v => new DirectoryInfo($"{v.PathIndie}{subFolder}"));
if (suitableVersions.Any())
{
var selectedVersion = suitableVersions
.OrderByDescending(dir => dir.Exists ? dir.LastWriteTimeUtc : DateTime.MinValue)
.ThenByDescending(dir => dir.Exists ? dir.GetFiles().Length : -1).First(); // 先按文件夹更改时间降序
// 再按文件夹中的文件数量降序
defaultFolder = selectedVersion.FullName;
Directory.CreateDirectory(defaultFolder);
ModBase.Log($"[Comp] 使用适合的游戏实例作为默认下载位置:{defaultFolder}");
}
else
{
defaultFolder = ModFolder.mcFolderSelected;
if (needLoad)
HintService.Hint(Lang.Text("Download.Comp.Detail.NoApplicableInstance"));
else
ModBase.Log("[Comp] 由于当前实例不兼容,使用当前的 MC 文件夹作为默认下载位置");
}
}
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Comp.Detail.SelectWorldInstallLocation"), file.FileName, Lang.Text("Download.Comp.Detail.WorldFile.Filter"),
defaultFolder);
if (string.IsNullOrEmpty(target))
return;
// 构造步骤加载器
var loaders = new List<ModLoader.LoaderBase>();
var targetPath = target.BeforeLast(@"\");
var logoFileAddress = MyImage.GetTempPath(_compItem.Logo);
loaders.Add(new LoaderDownload(Lang.Text("Download.Comp.Detail.DownloadWorldFile"),
new List<DownloadFile>
{
file.ToNetFile(target, ModComp.DownloadReason.Standalone, file.RawGameVersions.FirstOrDefault())
})
{ ProgressWeight = 10d, block = true });
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Download.Comp.Detail.InstallWorld"),
_ => ModBase.ExtractFile(target, targetPath, Encoding.UTF8)) { ProgressWeight = 0.1d, block = true });
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Download.Comp.Detail.CleanCache"),
_ => System.IO.File.Delete(target)));
// 启动
var loader = new ModLoader.LoaderCombo<int>(loaderName, loaders)
{ OnStateChanged = ModDownloadLib.LoaderStateChangedHintOnly };
loader.Start();
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"下载世界资源失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Comp.Error.OperationFailed"));
}
}
public void Save_Click(object sender, EventArgs e)
{
// 获取点击项关联的文件对象
var file = sender switch
{
FrameworkElement Element when Element.Tag is ModComp.CompFile CompFile => CompFile,
FrameworkElement Element when Element.Parent is FrameworkElement Parent && Parent.Tag is ModComp.CompFile CompFile => CompFile,
FrameworkElement Element when Element.Parent is FrameworkElement Parent && Parent.Parent is FrameworkElement GrandParent && GrandParent.Tag is ModComp.CompFile CompFile => CompFile,
_ => null
};
ModBase.RunInNewThread(() =>
{
try
{
var desc = file.Type switch
{
ModComp.CompType.ModPack => Lang.Text("Download.Comp.Type.Modpack"),
ModComp.CompType.Mod => Lang.Text("Download.Comp.Type.Mod"),
ModComp.CompType.ResourcePack => Lang.Text("Download.Comp.Type.ResourcePack"),
ModComp.CompType.Shader => Lang.Text("Download.Comp.Type.Shader"),
ModComp.CompType.DataPack => Lang.Text("Download.Comp.Type.DataPack"),
ModComp.CompType.World => Lang.Text("Download.Comp.Type.World"),
_ => ""
};
// 确认默认保存位置
string defaultFolder = null;
var allowedLoaders = new List<ModComp.CompLoaderType>();
if (file.Type != ModComp.CompType.ModPack)
{
var subFolder = "";
switch (file.Type)
{
case ModComp.CompType.Mod: subFolder = "mods\\"; break;
case ModComp.CompType.ResourcePack: subFolder = "resourcepacks\\"; break;
case ModComp.CompType.Shader: subFolder = "shaderpacks\\"; break;
case ModComp.CompType.World: subFolder = "saves\\"; break;
case ModComp.CompType.DataPack: subFolder = ""; break; // 导航到版本根目录
}
// 获取资源所需的加载器
if (file.ModLoaders.Any())
allowedLoaders = file.ModLoaders;
else if (_project.ModLoaders.Any()) allowedLoaders = _project.ModLoaders;
ModBase.Log(
$"[Comp] {desc}要求的加载器种类:{(allowedLoaders.Any() ? string.Join(" / ", allowedLoaders) : "")}");
// 判断某个版本是否符合资源要求 (局部函数)
Func<McInstance, bool> isVersionSuitable = version =>
{
if (version is null) return false;
if (!version.IsLoaded) version.Load();
// 只对 Mod 和数据包进行版本检测
if (file.Type == ModComp.CompType.Mod || file.Type == ModComp.CompType.DataPack)
if (file.GameVersions.Any(v => v.Contains(".")) &&
!file.GameVersions.Any(v => v.Contains(".") && v == version.Info.VanillaName))
return false;
// 加载器判定
if (!allowedLoaders.Any()) return true; // 无要求
if (allowedLoaders.Contains(ModComp.CompLoaderType.Forge) && version.Info.HasForge) return true;
if (allowedLoaders.Contains(ModComp.CompLoaderType.Forge) && version.Info.HasCleanroom)
return true;
if (allowedLoaders.Contains(ModComp.CompLoaderType.Fabric) &&
(version.Info.HasFabric || version.Info.HasLegacyFabric)) return true;
if (allowedLoaders.Contains(ModComp.CompLoaderType.NeoForge) && version.Info.HasNeoForge)
return true;
if (allowedLoaders.Contains(ModComp.CompLoaderType.LiteLoader) && version.Info.HasLiteLoader)
return true;
return false;
};
// 获取常规资源默认下载位置逻辑
if (cachedFolder.ContainsKey(file.Type) && !string.IsNullOrEmpty(cachedFolder[file.Type]))
{
defaultFolder = cachedFolder.GetOrDefault(file.Type,
ModInstanceList.McMcInstanceSelected?.PathIndie ?? ModBase.exePath);
ModBase.Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{defaultFolder}");
}
else if (ModInstanceList.McMcInstanceSelected is not null &&
isVersionSuitable(ModInstanceList.McMcInstanceSelected))
{
defaultFolder = $"{ModInstanceList.McMcInstanceSelected.PathIndie}{subFolder}";
Directory.CreateDirectory(defaultFolder);
ModBase.Log($"[Comp] 使用当前实例作为默认下载位置:{defaultFolder}");
}
else
{
// 查找所有可能的实例
var needLoad = ModInstanceList.mcInstanceListLoader.State != ModBase.LoadState.Finished;
if (needLoad)
{
HintService.Hint(Lang.Text("Download.Comp.Detail.FindingApplicableInstance"));
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
ModLoader.LoaderFolderRunType.ForceRun, 1, "versions\\", true);
}
var suitableVersions = ModInstanceList.mcInstanceList.Values.SelectMany(l => l)
.Where(v => isVersionSuitable(v))
.Select(v => new DirectoryInfo($"{v.PathIndie}{subFolder}"));
if (suitableVersions.Any())
{
var selectedVersion = suitableVersions
.OrderByDescending(dir => dir.Exists ? dir.LastWriteTimeUtc : DateTime.MinValue)
.ThenByDescending(dir => dir.Exists ? dir.GetFiles().Length : -1)
.First();
defaultFolder = selectedVersion.FullName;
Directory.CreateDirectory(defaultFolder);
ModBase.Log($"[Comp] 使用适合的游戏实例作为默认下载位置:{defaultFolder}");
}
else
{
defaultFolder = ModFolder.mcFolderSelected;
if (needLoad)
HintService.Hint(Lang.Text("Download.Comp.Detail.NoApplicableInstance"));
else
ModBase.Log("[Comp] 由于当前实例不兼容,使用当前的 MC 文件夹作为默认下载位置");
}
}
}
// 获取文件名并弹窗
var fileName = ModComp.CompFileNameGet(_project, file);
ModBase.RunInUi(() =>
{
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Comp.Detail.SelectSaveLocation"),
fileName, Lang.Text("Download.Comp.Detail.ResourceFile.Filter", desc) + "|" +
(file.Type == ModComp.CompType.Mod
? file.FileName.EndsWith(".litemod") ? "*.litemod" : "*.jar"
: file.FileName.EndsWith(".mrpack")
? "*.mrpack"
: "*.zip"),
defaultFolder);
if (!target.Contains("\\")) return;
// 记录缓存路径
var targetDir = ModBase.GetPathFromFullPath(target);
if (target != defaultFolder)
{
if (cachedFolder.ContainsKey(file.Type))
cachedFolder[file.Type] = targetDir;
else
cachedFolder.Add(file.Type, targetDir);
}
if (file.Type == ModComp.CompType.Mod && Config.Download.Comp.AutoInstallDependencies &&
file.Dependencies.Any())
{
try
{
McInstance? targetInstance = null;
var knownInstances = new List<McInstance>();
if (ModInstanceList.McMcInstanceSelected is not null)
{
knownInstances.Add(ModInstanceList.McMcInstanceSelected);
}
knownInstances.AddRange(ModInstanceList.mcInstanceList.Values.SelectMany(list => list)
.Where(instance => instance is not null));
targetInstance = knownInstances
.Distinct()
.FirstOrDefault(instance =>
targetDir.StartsWith(instance.PathIndie, StringComparison.OrdinalIgnoreCase));
if (targetInstance is not null && !targetInstance.IsLoaded)
{
targetInstance.Load();
}
var mcVersion = targetInstance?.Info?.VanillaName
?? file.GameVersions.FirstOrDefault(version => version.Contains("."))
?? string.Empty;
var targetLoaders = new List<ModComp.CompLoaderType>();
if (targetInstance is not null)
{
if (targetInstance.Info.HasForge || targetInstance.Info.HasCleanroom)
targetLoaders.Add(ModComp.CompLoaderType.Forge);
if (targetInstance.Info.HasFabric || targetInstance.Info.HasLegacyFabric)
targetLoaders.Add(ModComp.CompLoaderType.Fabric);
if (targetInstance.Info.HasQuilt)
targetLoaders.Add(ModComp.CompLoaderType.Quilt);
if (targetInstance.Info.HasNeoForge)
targetLoaders.Add(ModComp.CompLoaderType.NeoForge);
if (targetInstance.Info.HasLiteLoader)
targetLoaders.Add(ModComp.CompLoaderType.LiteLoader);
}
if (!targetLoaders.Any())
{
targetLoaders = allowedLoaders.ToList();
}
ModBase.Log($"[CompDeps] 开始解析必需前置: {file.Dependencies.Count} 个依赖");
var request = ModCompDependency.BuildRequest(file, _project, mcVersion, targetLoaders,
targetDir);
var resolver = new ModDependencyResolver();
var result = resolver.Resolve(request);
void DownloadDependencies()
{
if (!result.ToInstall.Any())
{
ModBase.Log("[CompDeps] 所有前置均无法解析,仅下载 Mod 本体");
return;
}
ModBase.Log($"[CompDeps] 准备下载: {result.ToInstall.Count} 个前置");
var depDownloads = ModCompDependency.BuildDependencyDownloads(result, targetDir);
foreach (var (depFilename, downloadFile) in depDownloads)
{
var depLoaderName = Lang.Text("Download.Comp.Detail.DownloadResource", desc,
ModBase.GetFileNameWithoutExtentionFromPath(depFilename));
var depLoaders = new List<ModLoader.LoaderBase>
{
new LoaderDownload(Lang.Text("Download.Comp.Detail.DownloadFile"),
new List<DownloadFile> { downloadFile })
{
ProgressWeight = 6,
block = true
}
};
// 启动加载器
var depLoader = new ModLoader.LoaderCombo<int>(depLoaderName, depLoaders);
depLoader.OnStateChanged = ModDownloadLib.LoaderStateChangedHintOnly;
depLoader.Start(1);
ModLoader.LoaderTaskbarAdd(depLoader);
}
}
if (result.Unresolved.Any() || result.ToInstall.Any())
{
var installChoice = ModCompDependency.ConfirmDependencyInstall(result);
switch (installChoice)
{
case ModComp.CompDepsInstallTypes.Unresolved:
ModBase.Log("[CompDeps] 发现无法解析的前置");
DownloadDependencies();
break;
case ModComp.CompDepsInstallTypes.WithDeps:
DownloadDependencies();
break;
case ModComp.CompDepsInstallTypes.WithoutDeps:
ModBase.Log("[CompDeps] 用户选择仅下载 Mod 本体,跳过前置下载");
break;
case ModComp.CompDepsInstallTypes.Cancel:
ModBase.Log("[CompDeps] 用户取消安装");
return;
default:
ModBase.Log($"[CompDeps] 未知返回值: {installChoice} ,终止下载");
return;
}
}
else
{
ModBase.Log("[CompDeps] 已满足: 所有必需前置已安装");
}
}
catch (Exception depEx)
{
ModBase.Log(depEx, "[CompDeps] 依赖解析失败,跳过前置安装");
var message = ExceptionDetails.Compose(
Lang.Text("Download.Comp.Dependency.ResolveFailed.Message"),
depEx);
ModMain.MyMsgBox(
message,
Lang.Text("Download.Comp.Dependency.ResolveFailed.Title"),
Lang.Text("Download.Comp.Dependency.ResolveFailed.Continue"),
isWarn: true,
forceWait: true);
}
}
// 构造下载任务
var loaderName = Lang.Text("Download.Comp.Detail.DownloadResource", desc,
ModBase.GetFileNameWithoutExtentionFromPath(target));
var loaders = new List<ModLoader.LoaderBase>
{
new LoaderDownload(Lang.Text("Download.Comp.Detail.DownloadFile"),
new List<DownloadFile>
{
file.Type == ModComp.CompType.Mod
? file.ToNetFile(target)
: file.ToNetFile(target, ModComp.DownloadReason.Standalone, null)
})
{
ProgressWeight = 6,
block = true
}
};
// 启动加载器
var loader = new ModLoader.LoaderCombo<int>(loaderName, loaders);
loader.OnStateChanged = ModDownloadLib.LoaderStateChangedHintOnly;
loader.Start(1);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
});
}
catch (Exception ex)
{
ModBase.Log(
ex,
"保存资源文件失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Comp.Error.OperationFailed"));
}
}, "Download CompDetail Save");
}
private void BtnIntroWeb_Click(object sender, EventArgs e)
{
ModBase.OpenWebsite(_project.Website);
}
private void BtnIntroWiki_Click(object sender, EventArgs e)
{
ModBase.OpenWebsite("https://www.mcmod.cn/class/" + _project.WikiId + ".html");
}
private void BtnIntroCopy_Click(object sender, EventArgs e)
{
ModBase.ClipboardSet(_compItem.LabTitle.Text + _compItem.LabTitleRaw.Text);
}
private void BtnFavorites_Click(object sender, EventArgs e)
{
ModComp.CompFavorites.ShowMenu(_project, (UIElement)sender, RefreshFavoriteButton);
}
private void BtnIntroLinkCopy_Click(object sender, EventArgs e)
{
ModComp.CompClipboard.currentText = _project.Website;
ModBase.ClipboardSet(_project.Website);
}
// 翻译简介
private async void BtnTranslate_Click(object sender, EventArgs e)
{
HintService.Hint(Lang.Text("Download.Comp.Detail.DescriptionTranslating", _project.TranslatedName));
var chineseDescription = await _project.ChineseDescription;
if (chineseDescription is null)
return;
ModMain.MyMsgBox(Lang.Text("Download.Comp.Detail.DescriptionTranslationResult", _project.Description,
chineseDescription));
}
/// <summary>
/// 刷新收藏按钮的显示状态
/// </summary>
public void RefreshFavoriteButton()
{
try
{
if (_project is null) return;
var isFavourite = ModComp.CompFavorites.IsFavourite(_project.Id);
BtnFavorites.SvgIcon = isFavourite ? "lucide/heart-filled" : "lucide/heart";
// 刷新顶部的项目卡片收藏状态
if (_compItem is not null)
_compItem.RefreshFavoriteStatus();
}
catch (Exception ex)
{
ModBase.Log(ex, "刷新收藏按钮状态时出错");
}
}
#region
private readonly ModLoader.LoaderTask<int, List<ModComp.CompFile>> _compFileLoader;
public PageDownloadCompDetail()
{
_compFileLoader = new ModLoader.LoaderTask<int, List<ModComp.CompFile>>("Comp File", task =>
{
LoadTargetFromAdditional();
var result = ModComp.CompFilesGet(_project.Id, _project.FromCurseForge);
if (task.IsAborted)
return;
task.output = result;
});
Initialized += PageDownloadCompDetail_Inited;
Loaded += (_, _) => LoadTargetFromAdditional();
PageEnter += Init;
InitializeComponent();
Load.StateChanged += Load_State;
BtnIntroWeb.Click += BtnIntroWeb_Click;
BtnIntroWiki.Click += BtnIntroWiki_Click;
BtnIntroCopy.Click += BtnIntroCopy_Click;
BtnFavorites.Click += BtnFavorites_Click;
BtnIntroLinkCopy.Click += BtnIntroLinkCopy_Click;
BtnTranslate.Click += BtnTranslate_Click;
}
// 初始化加载器信息
private void PageDownloadCompDetail_Inited(object sender, EventArgs e)
{
LoadTargetFromAdditional();
PageLoaderInit(Load, PanLoad, PanMain, CardIntro, _compFileLoader, _ => Load_OnFinish());
}
public void LoadTargetFromAdditional()
{
var additional = ModMain.frmMain.pageCurrent.additional.Value;
_project = additional.CompProject;
_targetInstance = additional.TargetVersion;
_targetLoader = additional.TargetLoader;
_pageType = additional.ResourceType;
}
private ModComp.CompProject _project;
private string _targetInstance;
private ModComp.CompLoaderType _targetLoader;
/// <summary>
/// 当前页面应展示的内容类别。可能为 Any。
/// </summary>
private ModComp.CompType _pageType;
// 自动重试
private void Load_State(object sender, MyLoading.MyLoadingState state, MyLoading.MyLoadingState oldState)
{
switch (_compFileLoader.State)
{
case ModBase.LoadState.Failed:
{
var errorMessage = "";
if (_compFileLoader.Error is not null)
errorMessage = _compFileLoader.Error.Message;
if (errorMessage.Contains(Lang.Text("Common.Error.InvalidJson")))
{
ModBase.Log("[Comp] 下载的文件 Json 列表损坏,已自动重试", ModBase.LogLevel.Debug);
PageLoaderRestart();
}
break;
}
}
}
// 结果 UI 化
private class CardSorter : IComparer<string>
{
public readonly string topmost = "";
public CardSorter(string topmost = "")
{
this.topmost = topmost ?? "";
}
public int Compare(string x, string y)
{
// 相同
if ((x ?? "") == (y ?? ""))
return 0;
// 置顶
if ((x ?? "") == (topmost ?? ""))
return -1;
if ((y ?? "") == (topmost ?? ""))
return 1;
// 特殊版本
var isXSpecial = !x.Contains(".");
var isYSpecial = !y.Contains(".");
if (isXSpecial && isYSpecial)
return string.Compare(x, y, StringComparison.Ordinal);
if (isXSpecial)
return 1;
if (isYSpecial)
return -1;
// 比较版本号
var versionCodeSort = -McVersionComparer.CompareVersion(x.Replace(x.BeforeFirst(" ") + " ", ""),
y.Replace(y.BeforeFirst(" ") + " ", ""));
if (versionCodeSort != 0)
return versionCodeSort;
// 比较全部
return -McVersionComparer.CompareVersion(x, y);
}
}
private string? _instanceFilter;
private string? _modLoaderFilter;
private bool groupedDrop; // 是否按 Drop 筛选(1.21 / 1.20 / 1.19 / ...)而非小版本号(1.21.1 / 1.21 / 1.20.4 / ...
private bool groupedOld; // 是否折叠远古版本为一个选项
// 筛选类型相同的结果(Modrinth 会返回 Mod、服务端插件、数据包混合的列表)
private List<ModComp.CompFile> GetResults()
{
var results = _compFileLoader.output;
if (_pageType == ModComp.CompType.Any)
{
results = results.Where(r => r.Type != ModComp.CompType.Plugin).ToList();
}
else if (_pageType == ModComp.CompType.Shader || _pageType == ModComp.CompType.ResourcePack)
{
}
// 不筛选光影和资源包,否则原版光影会因为是资源包格式而被过滤(Meloong-Git/#6473
else
{
results = results.Where(r => r.Type == _pageType).ToList();
}
return results;
}
private void Load_OnFinish()
{
var results = GetResults();
// 初始化筛选器
List<string> instanceFilters = null;
List<string> modLoaderFilters = null;
void updateFilters()
{
instanceFilters = results.SelectMany(v => v.GameVersions)
.Select(v => GetGroupedVersionName(v, groupedDrop, groupedOld)).Distinct()
.OrderByDescending(s => s, new McVersionComparer.VersionComparer()).ToList();
modLoaderFilters = results.SelectMany(v => v.ModLoaders).Select(l => l.ToString()).Distinct()
.OrderByDescending(s => s).ToList();
}
;
// 确定分组方式
groupedDrop = false;
groupedOld = false;
updateFilters();
if (instanceFilters.Count >= 9)
{
groupedDrop = true;
groupedOld = false;
updateFilters();
if (instanceFilters.Count >= 9)
{
groupedDrop = false;
groupedOld = true;
updateFilters();
if (instanceFilters.Count >= 9)
{
groupedDrop = true;
groupedOld = true;
updateFilters();
}
}
}
// UI 化筛选器
PanInstanceFilter.Children.Clear();
PanModLoaderFilter.Children.Clear();
if (_pageType == ModComp.CompType.Mod)
{
PanInstanceFilter.Margin = new Thickness(10d, 10d, 0d, 5d);
PanModLoaderFilter.Margin = new Thickness(10d, 5d, 0d, 10d);
}
else
{
PanInstanceFilter.Margin = new Thickness(10d, 10d, 0d, 10d);
PanModLoaderFilter.Margin = new Thickness(0d);
}
if (instanceFilters.Count < 2)
{
CardFilter.Visibility = Visibility.Collapsed;
_instanceFilter = null;
}
else
{
CardFilter.Visibility = Visibility.Visible;
// 插入标签
if (_pageType == ModComp.CompType.Mod)
{
var instanceTextBlock = new TextBlock
{
Text = Lang.Text("Download.Comp.Detail.InstanceFilter"),
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(2d, 0d, 0d, 0d)
};
PanInstanceFilter.Children.Add(instanceTextBlock);
var modLoaderTextBlock = new TextBlock
{
Text = Lang.Text("Download.Comp.Detail.LoaderFilter"),
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(2d, 0d, 0d, 0d)
};
PanModLoaderFilter.Children.Add(modLoaderTextBlock);
}
instanceFilters.Insert(0, Lang.Text("Common.Option.All"));
modLoaderFilters.Insert(0, Lang.Text("Common.Option.All"));
// 转化为按钮
foreach (var version in instanceFilters)
{
var newButton = new MyRadioButton
{
Text = version, Margin = new Thickness(2d, 0d, 2d, 0d),
ColorType = MyRadioButton.ColorState.Highlight
};
newButton.LabText.Margin = new Thickness(-2, 0d, 10d, 0d);
newButton.Check += (sender, raiseByMouse) =>
{
_instanceFilter = sender.Text == Lang.Text("Common.Option.All") ? null : sender.Text;
UpdateFilterResult();
};
PanInstanceFilter.Children.Add(newButton);
}
if (_pageType == ModComp.CompType.Mod)
foreach (var loader in modLoaderFilters)
{
var newButton = new MyRadioButton
{
Text = loader,
Margin = new Thickness(2d, 0d, 2d, 0d),
ColorType = MyRadioButton.ColorState.Highlight
};
newButton.LabText.Margin = new Thickness(-2, 0d, 10d, 0d);
newButton.Check += (sender, raiseByMouse) =>
{
_modLoaderFilter = sender.Text == Lang.Text("Common.Option.All") ? null : sender.Text;
UpdateFilterResult();
};
PanModLoaderFilter.Children.Add(newButton);
}
// 自动选择
MyRadioButton instanceToCheck = null;
MyRadioButton modLoaderToCheck = null;
if (!string.IsNullOrEmpty(_targetInstance))
{
var targetFile = results.FirstOrDefault(v => v.GameVersions.Contains(_targetInstance));
if (targetFile is not null)
{
var targetGroup = GetGroupedVersionName(_targetInstance, groupedDrop, groupedOld);
var children = _pageType == ModComp.CompType.Mod
? PanInstanceFilter.Children.Cast<UIElement>().Skip(1)
: PanInstanceFilter.Children.Cast<UIElement>();
foreach (MyRadioButton button in (IEnumerable)children)
{
if ((button.Text ?? "") != (targetGroup ?? ""))
continue;
instanceToCheck = button;
break;
}
}
}
if (_pageType == ModComp.CompType.Mod)
if (_targetLoader != ModComp.CompLoaderType.Any)
{
var targetFile = results.FirstOrDefault(v => v.ModLoaders.Contains(_targetLoader));
if (targetFile is not null)
{
var children = _pageType == ModComp.CompType.Mod
? PanInstanceFilter.Children.Cast<UIElement>().Skip(1)
: PanInstanceFilter.Children.Cast<UIElement>();
foreach (MyRadioButton button in (IEnumerable)children)
{
if ((button.Text ?? "") != (_targetLoader.ToString() ?? ""))
continue;
modLoaderToCheck = button;
break;
}
}
}
// 注意:在 Mod 下 index 0 是 TextBlock
var index = _pageType == ModComp.CompType.Mod ? 1 : 0;
if (instanceToCheck is null)
instanceToCheck = (MyRadioButton)PanInstanceFilter.Children[index];
if (modLoaderToCheck is null && (_pageType == ModComp.CompType.Mod))
modLoaderToCheck = (MyRadioButton)PanModLoaderFilter.Children[index];
instanceToCheck.Checked = true;
if (_pageType == ModComp.CompType.Mod)
modLoaderToCheck.Checked = true;
}
// 更新筛选结果(文件列表 UI 化)
UpdateFilterResult();
}
private void UpdateFilterResult()
{
var results = GetResults();
if (results is null)
return;
// 1. 预处理基础变量
var targetVersionText = _targetLoader != ModComp.CompLoaderType.Any ? _targetLoader + " " : "";
var targetCardName = !string.IsNullOrEmpty(_targetInstance) || _targetLoader != ModComp.CompLoaderType.Any
? Lang.Text("Download.Comp.Detail.SelectedVersion", targetVersionText, _targetInstance)
: "";
// 使用 HashSet 提高查询性能 O(1)
var supportedLoaders =
new HashSet<ModComp.CompLoaderType>(Enum.GetValues(typeof(ModComp.CompLoaderType))
.Cast<ModComp.CompLoaderType>());
var ignoreQuilt = Config.Download.Comp.IgnoreQuilt;
var hasMultipleLoaders = _project.ModLoaders.Count > 1;
// 2. 核心数据归类 (使用 Dictionary 配合 HashSet 去重)
var dict = new SortedDictionary<string, List<ModComp.CompFile>>(new CardSorter(targetCardName));
dict.Add(Lang.Text("Download.Comp.Detail.VersionGroup.Other"), new List<ModComp.CompFile>());
// 用于记录每个卡片内已存在的 version,防止 Contains(version) 的 O(n) 消耗
var versionDuplicateChecker = new Dictionary<string, HashSet<ModComp.CompFile>>();
foreach (var version in results)
{
// 处理普通卡片归类
foreach (var gameVersion in version.GameVersions)
{
// 筛选器预检查
var currentGroupedName = GetGroupedVersionName(gameVersion, groupedDrop, groupedOld);
if (_instanceFilter is not null && (currentGroupedName ?? "") != (_instanceFilter ?? ""))
continue;
var verName = GetGroupedVersionName(gameVersion, false, false);
var loaders = new List<string>();
// 判定 Loader 逻辑
if (hasMultipleLoaders && version.Type == ModComp.CompType.Mod &&
McInstanceInfo.IsFormatFit(verName))
{
foreach (var loader in version.ModLoaders)
{
if (loader == ModComp.CompLoaderType.Quilt && ignoreQuilt)
continue;
if (!supportedLoaders.Contains(loader))
continue;
// 模组加载器筛选器
if (_modLoaderFilter is not null && (loader.ToString() ?? "") != (_modLoaderFilter ?? ""))
continue;
loaders.Add(loader + " ");
}
if (loaders.Count == 0 && _modLoaderFilter is not null) continue;
}
if (loaders.Count == 0)
loaders.Add("");
// 填充数据
foreach (var loaderPrefix in loaders)
{
var targetKey = loaderPrefix + verName;
AddVersionToDict(dict, versionDuplicateChecker, targetKey, version);
}
}
// 处理“所选版本”卡片 (逻辑合并,减少二次循环)
if (!string.IsNullOrEmpty(targetCardName))
{
var isMatchFilter = _instanceFilter is null ||
GetGroupedVersionName(_targetInstance, groupedDrop, groupedOld)
.StartsWithF(_instanceFilter);
if (isMatchFilter && version.GameVersions.Contains(_targetInstance))
if (_targetLoader == ModComp.CompLoaderType.Any || version.ModLoaders.Contains(_targetLoader))
// 再次检查 version 是否符合筛选器(针对该文件的所有游戏版本)
if (_instanceFilter is null || version.GameVersions.Any(v =>
(GetGroupedVersionName(v, groupedDrop, groupedOld) ?? "") == (_instanceFilter ?? "")))
AddVersionToDict(dict, versionDuplicateChecker, targetCardName, version);
}
}
// 3. 渲染 UI
try
{
PanResults.Children.Clear();
var additional = ModMain.frmMain.pageCurrent.additional;
var additionalTitles = additional is not null
? additional.Value.ExpandedTitles
: new List<string>();
foreach (var pair in dict)
{
if (pair.Value.Count == 0)
continue;
// 创建卡片组件
var newCard = new MyCard
{
Title = pair.Key,
Margin = new Thickness(0d, 0d, 0d, 15d)
};
// 闭包引用:避免在 Sub 内做高耗时操作
var files = pair.Value;
var currentKey = pair.Key;
var newStack = new StackPanel
{
Margin = new Thickness(20d, MyCard.SwapedHeight, 18d, 0d),
VerticalAlignment = VerticalAlignment.Top,
Tag = files
};
newCard.Children.Add(newStack);
newCard.SwapControl = newStack;
// 延迟加载安装项的逻辑
newCard.InstallMethod = stack =>
{
var list = (List<ModComp.CompFile>)stack.Tag;
// 排序和去重检查
list.Sort((a, b) => b.ReleaseDate.CompareTo(a.ReleaseDate));
var distinctCount = list.Select(f => f.DisplayName).Distinct().Count();
var badDisplayName = distinctCount != list.Count;
// 批量添加子项
switch (_project.Type)
{
case ModComp.CompType.ModPack:
{
foreach (var item in list)
stack.Children.Add(item.ToListItem(
(sender, e) => ModMain.frmDownloadCompDetail.Install_Click((MyListItem)sender, e),
ModMain.frmDownloadCompDetail.Save_Click, badDisplayName));
break;
}
case ModComp.CompType.World:
{
foreach (var item in list)
stack.Children.Add(item.ToListItem(
(sender, e) =>
ModMain.frmDownloadCompDetail.InstallWorld_Click((MyListItem)sender, e),
ModMain.frmDownloadCompDetail.Save_Click, badDisplayName));
break;
}
default:
{
ModComp.CompFilesCardPreload(stack, list);
foreach (var item in list)
stack.Children.Add(item.ToListItem(ModMain.frmDownloadCompDetail.Save_Click,
badDisplayName: badDisplayName));
break;
}
}
};
PanResults.Children.Add(newCard);
// 展开逻辑
if ((currentKey ?? "") == (targetCardName ?? "") || additionalTitles.Contains(newCard.Title))
newCard.StackInstall();
else
newCard.IsSwapped = true;
// 特殊提示
if (currentKey == Lang.Text("Download.Comp.Detail.VersionGroup.Other"))
newStack.Children.Add(new MyHint
{
Text = Lang.Text("Download.Comp.Detail.VersionRecognitionDelayHint"),
Theme = MyHint.Themes.Yellow,
Margin = new Thickness(5d, 0d, 0d, 8d)
});
}
// 单卡片自动展开
if (PanResults.Children.Count == 1) ((MyCard)PanResults.Children[0]).IsSwapped = false;
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化工程下载列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Comp.Error.OperationFailed"));
}
}
/// <summary>
/// 辅助方法:向字典添加数据并处理去重
/// </summary>
private void AddVersionToDict(SortedDictionary<string, List<ModComp.CompFile>> dict,
Dictionary<string, HashSet<ModComp.CompFile>> checker, string key, ModComp.CompFile version)
{
if (!dict.ContainsKey(key))
{
dict.Add(key, new List<ModComp.CompFile>());
checker.Add(key, new HashSet<ModComp.CompFile>());
}
// 使用 HashSet.Add 判断是否重复,比 List.Contains 快得多
if (checker[key].Add(version)) dict[key].Add(version);
}
private string GetGroupedVersionName(string name, bool groupedByDrop, bool foldOld)
{
if (name is null)
return Lang.Text("Download.Comp.Detail.VersionGroup.Other");
if (name.Contains('w'))
return Lang.Text("Download.Comp.Detail.VersionGroup.Snapshot");
if (foldOld && McInstanceInfo.VersionToDrop(name, true) < 120)
return Lang.Text("Download.Comp.Detail.VersionGroup.Old");
if (groupedByDrop)
{
var drop = McInstanceInfo.VersionToDrop(name, true);
if (drop >= 0)
return McInstanceInfo.DropToVersion(drop);
return name;
}
return name;
}
#endregion
}
@@ -0,0 +1,27 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadDataPack"
PanScroll="{Binding ElementName=Content}">
<local:PageComp x:Name="Content" PageType="DataPack">
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.WorldGen}" Tag="/worldgen" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Technology}" Tag="6951/technology" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.GameMechanics}" Tag="/game-mechanics" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Transportation}" Tag="/transportation" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Storage}" Tag="/storage" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Magic}" Tag="6952/magic" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Adventure}" Tag="6948/adventure" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.DataPack.Fantasy}" Tag="6949/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Decoration}" Tag="/decoration" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Mobs}" Tag="/mobs" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Utility}" Tag="6953/utility" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Equipment}" Tag="/equipment" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Optimization}" Tag="/optimization" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Server}" Tag="/social" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Library}" Tag="6950/library" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Mod.ModRelated}" Tag="6946/" />
</local:PageComp>
</local:MyPageRight>
@@ -0,0 +1,9 @@
namespace PCL;
public partial class PageDownloadDataPack
{
public PageDownloadDataPack()
{
InitializeComponent();
}
}
@@ -0,0 +1,37 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadMod"
PanScroll="{Binding ElementName=Content}">
<local:PageComp x:Name="Content" PageType="Mod">
<local:MyComboBoxItem Content="{DynamicResource Common.Option.All}" Tag="" IsSelected="True" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.WorldGen}" Tag="406/worldgen" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Biomes}" Tag="407/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Dimensions}" Tag="410/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.OresResources}" Tag="408/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Structures}" Tag="409/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Technology}" Tag="412/technology" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.PipesLogistics}" Tag="415/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Automation}" Tag="4843/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Energy}" Tag="417/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Redstone}" Tag="4558/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.FoodCooking}" Tag="436/food" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Farming}" Tag="416/" Margin="15,0,0,0" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.GameMechanics}" Tag="/game-mechanics" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Transportation}" Tag="414/transportation" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Storage}" Tag="420/storage" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Magic}" Tag="419/magic" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Adventure}" Tag="422/adventure" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Decoration}" Tag="424/decoration" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Mobs}" Tag="411/mobs" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Utility}" Tag="5191/utility" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Equipment}" Tag="434/equipment" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Creative}" Tag="9026/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Optimization}" Tag="6814/optimization" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Display}" Tag="423/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Server}" Tag="435/social" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Library}" Tag="421/library" />
</local:PageComp>
</local:MyPageRight>
@@ -0,0 +1,10 @@
namespace PCL;
public partial class PageDownloadMod
{
public PageDownloadMod()
{
InitializeComponent();
}
}
@@ -0,0 +1,29 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadPack"
PanScroll="{Binding ElementName=Content}">
<local:PageComp x:Name="Content" PageType="ModPack">
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
<local:MyComboBoxItem Tag="4484/" Content="{DynamicResource Download.Comp.Category.Multiplayer}" />
<local:MyComboBoxItem Tag="/optimization" Content="{DynamicResource Download.Comp.Category.Optimization}" />
<local:MyComboBoxItem Tag="4479/challenging" Content="{DynamicResource Download.Comp.Category.Modpack.Hardcore}" />
<local:MyComboBoxItem Tag="4483/combat" Content="{DynamicResource Download.Comp.Category.Combat}" />
<local:MyComboBoxItem Tag="4478/quests" Content="{DynamicResource Download.Comp.Category.Modpack.Quests}" />
<local:MyComboBoxItem Tag="4472/technology" Content="{DynamicResource Download.Comp.Category.Technology}" />
<local:MyComboBoxItem Tag="4473/magic" Content="{DynamicResource Download.Comp.Category.Magic}" />
<local:MyComboBoxItem Tag="4475/adventure" Content="{DynamicResource Download.Comp.Category.Adventure}" />
<local:MyComboBoxItem Tag="/kitchen-sink" Content="{DynamicResource Download.Comp.Category.Modpack.KitchenSink}" />
<local:MyComboBoxItem Tag="4476/" Content="{DynamicResource Download.Comp.Category.Modpack.Exploration}" />
<local:MyComboBoxItem Tag="4477/" Content="{DynamicResource Download.Comp.Category.Modpack.MiniGame}" />
<local:MyComboBoxItem Tag="4471/" Content="{DynamicResource Download.Comp.Category.Modpack.SciFi}" />
<local:MyComboBoxItem Tag="4736/" Content="{DynamicResource Download.Comp.Category.Modpack.Skyblock}" />
<local:MyComboBoxItem Tag="5128/" Content="{DynamicResource Download.Comp.Category.Modpack.VanillaPlus}" />
<local:MyComboBoxItem Tag="4487/" Content="{DynamicResource Download.Comp.Category.Modpack.Ftb}" ToolTip="{DynamicResource Download.Comp.Category.Modpack.Ftb.ToolTip}" ToolTipService.HorizontalOffset="70" />
<local:MyComboBoxItem Tag="4480/" Content="{DynamicResource Download.Comp.Category.Modpack.MapBased}" />
<local:MyComboBoxItem Tag="4481/lightweight" Content="{DynamicResource Download.Comp.Category.Modpack.SmallLight}" />
<local:MyComboBoxItem Tag="4482/" Content="{DynamicResource Download.Comp.Category.Modpack.ExtraLarge}" />
</local:PageComp>
</local:MyPageRight>
@@ -0,0 +1,9 @@
namespace PCL;
public partial class PageDownloadPack
{
public PageDownloadPack()
{
InitializeComponent();
}
}
@@ -0,0 +1,46 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadResourcePack"
PanScroll="{Binding ElementName=Content}">
<local:PageComp x:Name="Content" PageType="ResourcePack">
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Style}" IsEnabled="False" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.VanillaLike}" Tag="403/vanilla-like" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Realistic}" Tag="400/realistic" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Modern}" Tag="401/" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Medieval}" Tag="402/" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Steampunk}" Tag="399/" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Themed}" Tag="/themed" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Simplistic}" Tag="/simplistic" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Decoration}" Tag="/decoration" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Combat}" Tag="/combat" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Utility}" Tag="/utility" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Tweaks}" Tag="/tweaks" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Cursed}" Tag="/cursed" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Feature}" IsEnabled="False" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Entities}" Tag="/entities" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Audio}" Tag="/audio" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Fonts}" Tag="5244/fonts" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Models}" Tag="/models" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Locale}" Tag="/locale" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Gui}" Tag="/gui" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.CoreShaders}" Tag="/core-shaders" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Animated}" Tag="404/" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ModSupport}" Tag="4465/modded" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Resolution}" IsEnabled="False" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution8xOrLower}" Tag="/8x-" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution16x}" Tag="393/16x" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution32x}" Tag="394/32x" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution48x}" Tag="/48x" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution64x}" Tag="395/64x" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution128x}" Tag="396/128x" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution256x}" Tag="397/256x" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ResourcePack.Resolution512xOrHigher}" Tag="398/512x+" />
</local:PageComp>
</local:MyPageRight>
@@ -0,0 +1,9 @@
namespace PCL;
public partial class PageDownloadResourcePack
{
public PageDownloadResourcePack()
{
InitializeComponent();
}
}
@@ -0,0 +1,30 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadShader"
PanScroll="{Binding ElementName=Content}">
<local:PageComp x:Name="Content" PageType="Shader">
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Style}" IsEnabled="False" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.VanillaLike}" Tag="6555/vanilla-like" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Fantasy}" Tag="6554/fantasy" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Realistic}" Tag="6553/realistic" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.SemiRealistic}" Tag="/semi-realistic" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Cartoon}" Tag="/cartoon" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Feature}" IsEnabled="False" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.ColoredLighting}" Tag="/colored-lighting" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PathTracing}" Tag="/path-tracing" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Pbr}" Tag="/pbr" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.Reflections}" Tag="/reflections" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Performance}" IsEnabled="False" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PerformancePotato}" Tag="/potato" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PerformanceLow}" Tag="/low" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PerformanceMedium}" Tag="/medium" />
<local:MyComboBoxItem Margin="15,0,0,0" Content="{DynamicResource Download.Comp.Category.PerformanceHigh}" Tag="/high" />
</local:PageComp>
</local:MyPageRight>
@@ -0,0 +1,9 @@
namespace PCL;
public partial class PageDownloadShader
{
public PageDownloadShader()
{
InitializeComponent();
}
}
@@ -0,0 +1,19 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadWorld"
PanScroll="{Binding ElementName=Content}">
<local:PageComp x:Name="Content" PageType="World" SupportModrinth="False">
<local:MyComboBoxItem Tag="" Content="{DynamicResource Common.Option.All}" IsSelected="True" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Adventure}" Tag="248/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Creative}" Tag="249/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.Modpack.MiniGame}" Tag="250/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Parkour}" Tag="251/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Puzzle}" Tag="252/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.Survival}" Tag="253/" />
<local:MyComboBoxItem Content="{DynamicResource Download.Comp.Category.World.ModWorld}" Tag="4464/" />
</local:PageComp>
</local:MyPageRight>
@@ -0,0 +1,9 @@
namespace PCL;
public partial class PageDownloadWorld
{
public PageDownloadWorld()
{
InitializeComponent();
}
}
@@ -0,0 +1,4578 @@
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using PCL.Core.App;
using PCL.Core.App.Configuration;
using PCL.Core.App.Configuration.Storage;
using PCL.Core.App.Localization;
using PCL.Core.IO.Net.Http;
using PCL.Core.Minecraft;
using PCL.Core.UI;
using PCL.Core.Utils;
using PCL.Network;
using PCL.Network.Loaders;
namespace PCL;
public static class ModDownloadLib
{
/// <summary>
/// 如果 OptiFine 与 Forge 同时开始安装,就会导致 Forge 安装失败。
/// </summary>
private static readonly object installSyncLock = new();
/// <summary>
/// 如果 OptiFine 与 Forge 同时复制原版 Jar,就会导致复制文件时冲突。
/// </summary>
private static readonly object vanillaSyncLock = new();
/// <summary>
/// 将远程元数据提供的名称作为单个目录名拼接到缓存目录下,并阻止路径穿越。
/// </summary>
private static string CombineCacheSubfolder(string parentFolder, string childFolderName)
{
if (string.IsNullOrWhiteSpace(childFolderName) || childFolderName is "." or ".." ||
childFolderName.IndexOfAny(new[] { '/', '\\', ':', '*', '?', '"', '<', '>', '|', '\0' }) >= 0 ||
Path.IsPathRooted(childFolderName))
CancelUnsafeCacheSubfolder(childFolderName, "包含非法路径字符");
var parentFullPath = Path.GetFullPath(parentFolder)
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
var combinedFullPath = Path.GetFullPath(Path.Combine(parentFullPath, childFolderName));
if (!combinedFullPath.StartsWith(parentFullPath, StringComparison.OrdinalIgnoreCase))
CancelUnsafeCacheSubfolder(childFolderName, "导致缓存路径越界");
return combinedFullPath;
}
private static void CancelUnsafeCacheSubfolder(string childFolderName, string reason)
{
var message = "远程版本名" + reason + "" + childFolderName;
ModBase.Log("[Download] " + message);
HintService.Hint(message, HintType.Error);
throw new ModBase.CancelledException();
}
#region Minecraft
/// <summary>
/// 下载某个 Minecraft 实例,这会创造一个单独的下载任务,失败会跳过执行并要求反馈。
/// 返回正在下载的任务,若跳过或失败,则返回 Nothing。
/// </summary>
/// <param name="Id">所下载的 Minecraft 的版本名。</param>
/// <param name="JsonUrl">Json 文件的 Mojang 官方地址。</param>
public static ModLoader.LoaderCombo<string> McDownloadClient(NetPreDownloadBehaviour behaviour, string id,
string jsonUrl = null)
{
try
{
var versionFolder = Path.Combine(ModFolder.mcFolderSelected, "versions", id);
// 重复任务检查
foreach (var ongoingLoader in ModLoader.loaderTaskbar.ToList())
{
if (ongoingLoader.name != Lang.Text("Minecraft.Download.Stage.MinecraftDownload", id))
continue;
if (behaviour == NetPreDownloadBehaviour.ExitWhileExistsOrDownloading)
return (ModLoader.LoaderCombo<string>)ongoingLoader;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return (ModLoader.LoaderCombo<string>)ongoingLoader;
}
// 已有实例检查
if (behaviour != NetPreDownloadBehaviour.IgnoreCheck && File.Exists(Path.Combine(versionFolder, id + ".json")) &&
File.Exists(Path.Combine(versionFolder, id + ".jar")))
{
if (behaviour == NetPreDownloadBehaviour.ExitWhileExistsOrDownloading)
return null;
if (ModMain.MyMsgBox(
Lang.Text("Minecraft.Download.Error.InstanceAlreadyExists", id, "\r\n"),
Lang.Text("Minecraft.Download.Error.InstanceAlreadyExists.Title"),
Lang.Text("Common.Action.Continue"), Lang.Text("Common.Action.Cancel")
) == 1)
{
File.Delete(Path.Combine(versionFolder, id + ".jar"));
File.Delete(Path.Combine(versionFolder, id + ".json"));
}
else
{
return null;
}
}
// 启动
var loader =
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.MinecraftDownload", id),
McDownloadClientLoader(id, jsonUrl))
{ OnStateChanged = McInstallState };
loader.Start(versionFolder);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
return loader;
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 Minecraft 下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
return null;
}
}
/// <summary>
/// 保存某个 Minecraft 实例的核心文件(仅 Json 与核心 Jar)。
/// </summary>
/// <param name="id">所下载的 Minecraft 的版本名。</param>
/// <param name="jsonUrl">Json 文件的 Mojang 官方地址。</param>
public static void McDownloadClientCore(string id, string jsonUrl, NetPreDownloadBehaviour behaviour)
{
try
{
var versionFolder = SystemDialogs.SelectFolder();
if (!versionFolder.Contains(@"\"))
return;
versionFolder = Path.Combine(versionFolder, id);
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar)
{
if ((OngoingLoader.name ?? "") != (Lang.Text("Minecraft.Download.Stage.MinecraftDownload", id) ?? ""))
continue;
if (behaviour == NetPreDownloadBehaviour.ExitWhileExistsOrDownloading)
return;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
var loaders = new List<ModLoader.LoaderBase>();
// 下载实例 Json 文件
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadInstanceJson"),
new List<DownloadFile>
{
new(ModDownload.DlSourceLauncherOrMetaGet(jsonUrl), Path.Combine(versionFolder, id + ".json"),
new ModBase.FileChecker(canUseExistsFile: false, isJson: true))
}) { ProgressWeight = 2d });
// 获取支持库文件地址
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeCoreJarUrl"),
task => task.output =
ModLibrary.McLibNetFilesFromInstance(new McInstance(versionFolder)))
{ ProgressWeight = 0.5d, show = false });
// 下载支持库文件
loaders.Add(
new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadCoreJar"), new List<DownloadFile>())
{ ProgressWeight = 5d });
// 启动
var loader =
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.MinecraftDownload", id), loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(id);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 Minecraft 下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
/// <summary>
/// 获取下载某个 Minecraft 版本的加载器列表。
/// 它必须安装到 McFolderSelected,但是可以自定义版本名(不过自定义的实例名不会修改 Json 中的 id 项)。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadClientLoader(string id, string jsonUrl = null,
string instanceName = null)
{
instanceName = instanceName ?? id;
var instanceFolder = Path.Combine(ModFolder.mcFolderSelected, "versions", instanceName);
var loaders = new List<ModLoader.LoaderBase>();
// 下载实例 Json 文件
if (jsonUrl is null)
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.ObtainVanillaJsonUrl"), task =>
{
var jsonAddress = ModDownload.DlClientListGet(id)?.ToString();
task.output = new List<DownloadFile>
{
new(ModDownload.DlSourceLauncherOrMetaGet(jsonAddress), Path.Combine(instanceFolder, instanceName + ".json"))
};
})
{
ProgressWeight = 2d,
show = false
});
loaders.Add(new LoaderDownload(mcDownloadClientJsonName,
new List<DownloadFile>
{
new(ModDownload.DlSourceLauncherOrMetaGet(jsonUrl ?? ""), Path.Combine(instanceFolder, instanceName + ".json"),
new ModBase.FileChecker(canUseExistsFile: false, isJson: true))
}) { ProgressWeight = 3d });
// 下载支持库文件
var loadersLib = new List<ModLoader.LoaderBase>();
loadersLib.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeVanillaLibraries.Side"), task =>
{
var jsonPath = Path.Combine(instanceFolder, instanceName + ".json");
ModBase.WaitForFileReady(jsonPath);
ModBase.Log("[Download] 开始分析原版支持库文件:" + instanceFolder);
if (id == "1.16.5" && Config.Download.FixAuthLib) // 1.16.5 Authlib 修复
try
{
var json = ModBase.ReadFile(jsonPath);
json = json.Replace("2.1.28/authlib-2.1.28.jar", "2.3.31/authlib-2.3.31.jar")
.Replace("com.mojang:authlib:2.1.28", "com.mojang:authlib:2.3.31")
.Replace("ad54da276bf59983d02d5ed16fc14541354c71fd", "bbd00ca33b052f73a6312254780fc580d2da3535")
.Replace("76328", "87662");
ModBase.WriteFile(jsonPath, json);
}
catch (Exception ex)
{
ModBase.Log("[Download] 替换 Authlib 版本失败: " + ex.Message);
}
task.output = ModLibrary.McLibNetFilesFromInstance(new McInstance(instanceFolder));
})
{
ProgressWeight = 1d,
show = false
});
loadersLib.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadVanillaLibraries.Side"),
new List<DownloadFile>())
{ ProgressWeight = 13d, show = false });
loaders.Add(new ModLoader.LoaderCombo<string>(mcDownloadClientLibName, loadersLib)
{ block = false, ProgressWeight = 14d });
// 下载资源文件
var loadersAssets = new List<ModLoader.LoaderBase>();
loadersAssets.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeAssetsIndex.Side"), task =>
{
ModBase.WaitForFileReady(Path.Combine(instanceFolder, instanceName + ".json"));
try
{
var assetIndex = new McInstance(instanceFolder);
task.output = new List<DownloadFile> { ModDownload.DlClientAssetIndexGet(assetIndex) };
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.AssetIndexAnalysisFailed"), ex);
}
// 顺手添加 Json 项目
try
{
var versionJson = (JsonObject)ModBase.GetJson(ModBase.ReadFile(Path.Combine(instanceFolder, instanceName + ".json")));
versionJson.Add("clientVersion", id);
ModBase.WriteFile(Path.Combine(instanceFolder, instanceName + ".json"), versionJson.ToString());
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.AddClientVersionFailed"), ex);
}
})
{
ProgressWeight = 1d,
show = false
});
loadersAssets.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadAssetsIndex.Side"),
new List<DownloadFile>())
{ ProgressWeight = 3d, show = false });
loadersAssets.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeRequiredAssets.Side"), task =>
{
ModLoader.LoaderBase argprogressFeed = task;
task.output =
ModAssets.McAssetsFixList(new McInstance(instanceFolder), true, ref argprogressFeed);
task = (ModLoader.LoaderTask<string, List<DownloadFile>>)argprogressFeed;
})
{
ProgressWeight = 0.01d,
show = false
});
loadersAssets.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadAssets.Side"),
new List<DownloadFile>())
{ ProgressWeight = 14d, show = false });
loaders.Add(
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.DownloadVanillaAssets"),
loadersAssets) { block = false, ProgressWeight = 18d });
return loaders;
}
private static readonly string mcDownloadClientLibName = Lang.Text("Minecraft.Download.Stage.VanillaLibrariesDownload");
private static readonly string mcDownloadClientJsonName = Lang.Text("Minecraft.Download.Stage.VanillaJsonDownload");
#endregion
#region Minecraft
public static MyListItem McDownloadListItem(JsonObject entry, MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
// 确定图标
string logo = entry["type"].ToString() switch
{
"release" => ModBase.pathImage + "Blocks/Grass.png",
"snapshot" => ModBase.pathImage + "Blocks/CommandBlock.png",
"pending" => ModBase.pathImage + "Blocks/CommandBlock.png",
"special" => ModBase.pathImage + "Blocks/GoldBlock.png",
_ => ModBase.pathImage + "Blocks/CobbleStone.png"
};
// 建立控件
var formattedVersion = McFormatter.FormatVersion(entry["id"].ToString()).Replace("_", " ");
var newItem = new MyListItem
{
Logo = logo, SnapsToDevicePixels = true, Title = formattedVersion, Height = 42d,
Type = MyListItem.CheckType.Clickable, Tag = entry
};
if (entry["lore"] is null)
{
if (formattedVersion != (string)entry["id"])
newItem.Info = Lang.Date(entry["releaseTime"].ToObject<DateTime>(), "g") + " | " +
entry["id"];
else
newItem.Info = Lang.Date(entry["releaseTime"].ToObject<DateTime>(), "g");
}
else if (formattedVersion != (string)entry["id"])
{
newItem.Info = entry["lore"] + " | " + entry["id"];
}
else
{
newItem.Info = entry["lore"].ToString();
}
if (entry["url"].ToString().Contains("unlisted-versions-of-minecraft"))
newItem.Tags = Lang.Text("Download.Source.Tag.Uvmc");
newItem.Click += onClick;
// 建立菜单
if (isSaveOnly)
newItem.ContentHandler = McDownloadSaveMenuBuild;
else
newItem.ContentHandler = McDownloadMenuBuild;
// 结束
return newItem;
}
private static void McDownloadSaveMenuBuild(object sender, EventArgs _)
{
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (ss, ee) => McDownloadMenuLog(ss, (dynamic)ee);
var btnServer = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/server", ToolTip = Lang.Text("Download.Version.DownloadServer") };
ToolTipService.SetPlacement(btnServer, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnServer, 30d);
ToolTipService.SetHorizontalOffset(btnServer, 2d);
btnServer.Click += (ss, ee) => McDownloadMenuSaveServer(ss, (dynamic)ee);
((dynamic)sender).Buttons = new[] { btnServer, btnInfo };
}
private static void McDownloadMenuBuild(object sender, EventArgs e)
{
var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveAs") };
ToolTipService.SetPlacement(btnSave, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnSave, 30d);
ToolTipService.SetHorizontalOffset(btnSave, 2d);
btnSave.Click += (a, b) => McDownloadMenuSave(a, (dynamic)b); // dynamic!
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (a, b) => McDownloadMenuLog(a, (dynamic)b); // dynamic!
var btnServer = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/server", ToolTip = Lang.Text("Download.Version.DownloadServer") };
ToolTipService.SetPlacement(btnServer, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnServer, 30d);
ToolTipService.SetHorizontalOffset(btnServer, 2d);
btnServer.Click += (a, b) => McDownloadMenuSaveServer(a, (dynamic)b); // dynamic!
((dynamic)sender).Buttons = new[] { btnSave, btnInfo, btnServer };
}
private static void McDownloadMenuLog(object sender, RoutedEventArgs e)
{
JsonNode version;
if (((dynamic)sender).Tag is not null)
version = (JsonNode)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
version = (JsonNode)((dynamic)sender).Parent.Tag;
else
version = (JsonNode)((dynamic)sender).Parent.Parent.Tag;
McUpdateLogShow(version);
}
private static void McDownloadMenuSaveServer(object sender, RoutedEventArgs e)
{
MyListItem version;
if (sender is MyListItem)
version = (MyListItem)sender;
else if (((dynamic)sender).Parent is MyListItem)
version = (MyListItem)((dynamic)sender).Parent;
else
version = (MyListItem)((dynamic)sender).Parent.Parent;
try
{
var id = version.Title;
string jsonUrl = ((dynamic)version.Tag)["url"].ToString();
var versionFolder = SystemDialogs.SelectFolder();
if (!versionFolder.Contains(@"\"))
return;
versionFolder = Path.Combine(versionFolder, id);
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.MinecraftServerDownload", id) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.ServerDownloading"), HintType.Error);
return;
}
var loaders = new List<ModLoader.LoaderBase>();
// 下载实例 JSON 文件
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadInstanceJson"),
new List<DownloadFile>
{
new(ModDownload.DlSourceLauncherOrMetaGet(jsonUrl), Path.Combine(versionFolder, id + ".json"),
new ModBase.FileChecker(canUseExistsFile: false, isJson: true))
}) { ProgressWeight = 2d });
// 构建服务端
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.BuildServer"), task =>
{
// 分析服务端 JAR 文件下载地址
var mcInstance = new McInstance(versionFolder);
if (mcInstance.JsonObject["downloads"] is null ||
mcInstance.JsonObject["downloads"]["server"] is null ||
mcInstance.JsonObject["downloads"]["server"]["url"] is null)
{
File.Delete(Path.Combine(versionFolder, id + ".json"));
if (!new DirectoryInfo(versionFolder).GetFileSystemInfos().Any())
Directory.Delete(versionFolder);
task.output = new List<DownloadFile>();
HintService.Hint(Lang.Text("Minecraft.Download.Error.NoOfficialServerDownload", id),
HintType.Error);
Thread.Sleep(2000); // 等玩家把上一个提示看完
task.Abort();
return;
}
var jarUrl = (string)mcInstance.JsonObject["downloads"]["server"]["url"];
var checker = new ModBase.FileChecker(1024L,
(long)(mcInstance.JsonObject["downloads"]["server"]["size"] ?? -1),
(string)mcInstance.JsonObject["downloads"]["server"]["sha1"]);
task.output = new List<DownloadFile>
{ new(ModDownload.DlSourceLauncherOrMetaGet(jarUrl), Path.Combine(versionFolder, id + "-server.jar"), checker) };
// 添加启动脚本
var bat = $"""
@echo off
title {Lang.Text("Minecraft.Download.ServerBatch.Title", id)}
echo {Lang.Text("Minecraft.Download.ServerBatch.InstructionJavaPath")}
echo {Lang.Text("Minecraft.Download.ServerBatch.InstructionPclSettings")}
echo ------------------------------
echo {Lang.Text("Minecraft.Download.ServerBatch.InstructionEula")}
echo ------------------------------
"java" -server -XX:+UseG1GC -Xmx4096M -Xms1024M -XX:+UseCompressedOops -jar {id}-server.jar nogui
echo ----------------------
echo {Lang.Text("Minecraft.Download.ServerBatch.ServerStopped")}
pause
""";
ModBase.WriteFile(Path.Combine(versionFolder, "Launch Server.bat"), bat.Replace("\n", "\r\n"),
encoding: Encoding.Default.Equals(Encoding.UTF8) ? Encoding.UTF8 : Encoding.GetEncoding("GB18030"));
// 删除实例 JSON
File.Delete(Path.Combine(versionFolder, id + ".json"));
})
{
ProgressWeight = 0.5d,
show = false
});
// 下载服务端文件
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadServerFile"), [])
{ ProgressWeight = 5d });
// 启动
var loader =
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.MinecraftServerDownload", id),
loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(id);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 Minecraft 服务端下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
public static void McDownloadMenuSave(object sender, RoutedEventArgs e)
{
var element = (FrameworkElement)sender;
MyListItem version;
if (element is MyListItem s1) version = s1;
else if (element.Parent is MyListItem s2) version = s2;
else version = (MyListItem)((FrameworkElement)element.Parent).Parent;
try
{
var id = version.Title;
var jsonUrl = ((JsonObject)version.Tag)["url"]!.ToString();
var versionFolder = SystemDialogs.SelectFolder();
if (!versionFolder.Contains(@"\"))
return;
versionFolder = Path.Combine(versionFolder, id);
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") != (Lang.Text("Minecraft.Download.Stage.MinecraftDownload", id) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
var loaders = new List<ModLoader.LoaderBase>();
// 下载实例 JSON 文件
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadInstanceJson"),
new List<DownloadFile>
{
new(ModDownload.DlSourceLauncherOrMetaGet(jsonUrl), Path.Combine(versionFolder, id + ".json"),
new ModBase.FileChecker(canUseExistsFile: false, isJson: true))
}) { ProgressWeight = 2d });
// 获取支持库文件地址
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeCoreJarUrl"),
task => task.output = new List<DownloadFile>
{ ModDownload.DlClientJarGet(new McInstance(versionFolder), false) })
{ ProgressWeight = 0.5d, show = false });
// 下载支持库文件
loaders.Add(
new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadCoreJar"), new List<DownloadFile>())
{ ProgressWeight = 5d });
// 启动
var loader =
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.MinecraftDownload", id), loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(id);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 Minecraft 下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
/// <summary>
/// 显示某 Minecraft 版本的更新日志。
/// </summary>
/// <param name="versionJson">在 version_manifest.json 中的对应项。</param>
public static void McUpdateLogShow(JsonNode versionJson)
{
var wikiName = McFormatter.GetWikiUrlSuffix(versionJson["id"].ToString());
var wikiUrl = $"{McFormatter.GetWikiBaseUrl()}/{wikiName.TrimStart('/')}";
ModBase.OpenWebsite(wikiUrl);
}
#endregion
#region OptiFine
public static void McDownloadOptiFine(ModDownload.DlOptiFineListEntry downloadInfo)
{
try
{
var id = downloadInfo.NameVersion;
var versionFolder = Path.Combine(ModFolder.mcFolderSelected, "versions", id);
var isNewVersion = McVersionComparer.CompareVersionGe(downloadInfo.Inherit, "1.14");
var target = isNewVersion
? Path.Combine(ModBase.pathTemp, "Cache", "Code", downloadInfo.NameVersion + "_" + ModBase.GetUuid())
: Path.Combine(ModFolder.mcFolderSelected, "libraries", "optifine", "OptiFine",
downloadInfo.NameFile.Replace("OptiFine_", "").Replace(".jar", "").Replace("preview_", ""),
downloadInfo.NameFile.Replace("OptiFine_", "OptiFine-").Replace("preview_", ""));
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar)
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.OptiFineDownload", downloadInfo.DisplayName) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 已有实例检查
if (File.Exists(Path.Combine(versionFolder, id + ".json")))
{
if (ModMain.MyMsgBox(
Lang.Text("Minecraft.Download.Error.InstanceAlreadyExists", id, "\r\n"),
Lang.Text("Minecraft.Download.Error.InstanceAlreadyExists.Title"),
Lang.Text("Common.Action.Continue"), Lang.Text("Common.Action.Cancel")
) == 1)
{
File.Delete(Path.Combine(versionFolder, id + ".jar"));
File.Delete(Path.Combine(versionFolder, id + ".json"));
}
else
{
return;
}
}
// 启动
var loader =
new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.OptiFineDownload", downloadInfo.DisplayName),
McDownloadOptiFineLoader(downloadInfo)) { OnStateChanged = McInstallState };
loader.Start(versionFolder);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 OptiFine 下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
private static void McDownloadOptiFineSave(ModDownload.DlOptiFineListEntry downloadInfo)
{
try
{
var id = downloadInfo.NameVersion;
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Version.SelectSaveLocation"), downloadInfo.NameFile, "OptiFine Jar (*.jar)|*.jar");
if (!target.Contains(@"\"))
return;
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.OptiFineDownload", downloadInfo.DisplayName) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
var loader =
new ModLoader.LoaderCombo<ModDownload.DlOptiFineListEntry>(
Lang.Text("Minecraft.Download.Stage.OptiFineDownload", downloadInfo.DisplayName),
McDownloadOptiFineSaveLoader(downloadInfo, target))
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(downloadInfo);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 OptiFine 下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
private static void McDownloadOptiFineInstall(string baseMcFolderHome, string target, ModLoader.LoaderTask<List<DownloadFile>, bool> task, bool useJavaWrapper, Version javaVersion)
{
// 选择 Java
JavaEntry java;
lock (ModJava.javaLock)
{
java = ModJava.JavaSelect(Lang.Text("Minecraft.Download.Error.InstallationCanceled"),
javaVersion, enforceVersionRange: true);
if (java is null)
{
if (!ModJava.JavaDownloadConfirm(Lang.Text("Minecraft.Download.Error.JavaVersionRequired")))
throw new Exception(Lang.Text("Minecraft.Download.Error.JavaNotFoundInstallCanceled"));
// 开始自动下载
var downloadJavaMajor = javaVersion.Major >= 9 ? javaVersion.Major : 8;
var javaLoader = ModJava.GetJavaDownloadLoader();
try
{
javaLoader.Start(downloadJavaMajor, true);
while (javaLoader.State == ModBase.LoadState.Loading && !task.IsAborted)
Thread.Sleep(10);
}
finally
{
javaLoader.Abort(); // 确保取消时中止 Java 下载
}
// 检查下载结果
java = ModJava.JavaSelect(Lang.Text("Minecraft.Download.Error.InstallationCanceled"),
javaVersion, enforceVersionRange: true);
if (task.IsAborted)
return;
if (java is null)
throw new Exception(Lang.Text("Minecraft.Download.Error.JavaNotFoundInstallCanceled"));
}
}
// 添加 Java Wrapper 作为主 Jar
string arguments;
if (useJavaWrapper &&
!(dynamic)Config.Launch.DisableJlw) // dynamic!
arguments =
$"-Doolloo.jlw.tmpdir=\"{ModBase.pathPure.TrimEnd('\\')}\" -Duser.home=\"{baseMcFolderHome.TrimEnd('\\')}\" -cp \"{target}\" -jar \"{ModLaunch.ExtractJavaWrapper()}\" optifine.Installer";
else
arguments = $"-Duser.home=\"{baseMcFolderHome.TrimEnd('\\')}\" -cp \"{target}\" optifine.Installer";
if (java.Installation.MajorVersion >= 9)
arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " + arguments;
// 开始启动
lock (installSyncLock)
{
var info = new ProcessStartInfo
{
FileName = java.Installation.JavaExePath,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardError = true,
RedirectStandardOutput = true,
WorkingDirectory = ModBase.ShortenPath(baseMcFolderHome)
};
if (info.EnvironmentVariables.ContainsKey("appdata"))
info.EnvironmentVariables["appdata"] = baseMcFolderHome;
else
info.EnvironmentVariables.Add("appdata", baseMcFolderHome);
ModBase.Log("[Download] 开始安装 OptiFine" + target);
var totalLength = 0;
var process = new Process { StartInfo = info };
var lastResult = "";
using (var outputWaitHandle = new AutoResetEvent(false))
{
using (var errorWaitHandle = new AutoResetEvent(false))
{
process.OutputDataReceived += (_, e) =>
{
try
{
if (e.Data is null)
{
outputWaitHandle.Set();
}
else
{
lastResult = e.Data;
if (ModBase.modeDebug)
ModBase.Log("[Installer] " + lastResult);
totalLength += 1;
task.Progress += 0.9d / 7000d;
}
}
catch (ObjectDisposedException ex)
{
}
catch (Exception ex)
{
ModBase.Log(ex, "读取 OptiFine 安装器信息失败");
}
try
{
if (task.State == ModBase.LoadState.Aborted && !process.HasExited)
{
ModBase.Log("[Installer] 由于任务取消,已中止 OptiFine 安装");
process.Kill();
}
}
catch
{
}
};
process.ErrorDataReceived += (sender, e) =>
{
try
{
if (e.Data is null)
{
errorWaitHandle.Set();
}
else
{
lastResult = e.Data;
if (ModBase.modeDebug)
ModBase.Log("[Installer] " + lastResult);
totalLength += 1;
task.Progress += 0.9d / 7000d;
}
}
catch (ObjectDisposedException ex)
{
}
catch (Exception ex)
{
ModBase.Log(ex, "读取 OptiFine 安装器错误信息失败");
}
try
{
if (task.State == ModBase.LoadState.Aborted && !process.HasExited)
{
ModBase.Log("[Installer] 由于任务取消,已中止 OptiFine 安装");
process.Kill();
}
}
catch
{
}
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
// 等待
while (!process.HasExited)
Thread.Sleep(10);
// 输出
outputWaitHandle.WaitOne(10000);
errorWaitHandle.WaitOne(10000);
process.Dispose();
if (totalLength < 1000 || lastResult.Contains("at "))
throw new Exception(Lang.Text("Minecraft.Download.Error.InstallerFailedLastLine", lastResult));
}
}
}
}
/// <summary>
/// 获取下载某个 OptiFine 实例的加载器列表。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadOptiFineLoader(ModDownload.DlOptiFineListEntry downloadInfo,
string mcFolder = null, ModLoader.LoaderCombo<string> clientDownloadLoader = null, string clientFolder = null,
bool fixLibrary = true)
{
// 参数初始化
mcFolder = mcFolder ?? ModFolder.mcFolderSelected;
var isCustomFolder = (mcFolder ?? "") != (ModFolder.mcFolderSelected ?? "");
var id = downloadInfo.NameVersion;
var versionFolder = Path.Combine(mcFolder, "versions", id);
var isNewVersion = downloadInfo.Inherit.Contains("w") || McVersionComparer.CompareVersionGe(downloadInfo.Inherit, "1.14");
var target = isNewVersion
? $"{ModMain.RequestTaskTempFolder()}OptiFine.jar"
: $@"{mcFolder}libraries\optifine\OptiFine\{downloadInfo.NameFile.Replace("OptiFine_", "").Replace(".jar", "").Replace("preview_", "")}\{downloadInfo.NameFile.Replace("OptiFine_", "OptiFine-").Replace("preview_", "")}";
var loaders = new List<ModLoader.LoaderBase>();
var javaVersion = McVersionComparer.CompareVersionGe(downloadInfo.Inherit, "26.1.2")
? new Version(21, 0, 0, 0)
: new Version(1, 8, 0, 0);
// 获取下载地址
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.ObtainOptiFineUrl"), task =>
{
// 启动依赖实例的下载
if (clientDownloadLoader is null)
{
if (isCustomFolder)
throw new Exception(Lang.Text("Minecraft.Download.Error.NoVanillaLoaderSpecified"));
clientDownloadLoader = McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading,
downloadInfo.Inherit);
}
task.Progress = 0.1d;
var sources = new List<string>();
// BMCLAPI 源
var bmclapiInherit = downloadInfo.Inherit;
if (bmclapiInherit == "1.8" || bmclapiInherit == "1.9")
bmclapiInherit += ".0"; // #4281
if (downloadInfo.IsPreview)
sources.Add("https://bmclapi2.bangbang93.com/optifine/" + bmclapiInherit + "/HD_U_" +
downloadInfo.DisplayName.Replace(downloadInfo.Inherit + " ", "").Replace(" ", "/"));
else
sources.Add("https://bmclapi2.bangbang93.com/optifine/" + bmclapiInherit + "/HD_U/" +
downloadInfo.DisplayName.Replace(downloadInfo.Inherit + " ", ""));
// 官方源
string pageData;
try
{
using (var resp = HttpRequest
.Create("https://optifine.net/adloadx?f=" + downloadInfo.NameFile)
.WithHeader("Accept", "text/html")
.WithHeader("Accept-Language", "en-US,en;q=0.5")
.WithHeader("X-Requested-With", "XMLHttpRequest")
.SendAsync()
.GetAwaiter()
.GetResult())
{
resp.EnsureSuccessStatusCode();
pageData = resp.AsString();
}
task.Progress = 0.8d;
sources.Add("https://optifine.net/" + pageData.RegexSearch(@"downloadx\?f=[^""']+")[0]);
ModBase.Log("[Download] OptiFine " + downloadInfo.DisplayName + " 官方下载地址:" + sources.Last());
}
catch (Exception ex)
{
ModBase.Log(ex, "获取 OptiFine " + downloadInfo.DisplayName + " 官方下载地址失败");
}
// 构造文件请求
task.output = new List<DownloadFile>
{ new(sources.ToArray(), target, new ModBase.FileChecker(300 * 1024)) };
})
{
ProgressWeight = 8d
});
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadOptiFineMainFile"), [])
{ ProgressWeight = 8d });
loaders.Add(new ModLoader.LoaderTask<List<DownloadFile>, bool>(
Lang.Text("Minecraft.Download.Stage.WaitVanillaDownload"), task =>
{
// 等待原版文件下载完成
if (clientDownloadLoader is null)
return;
var targetLoaders = clientDownloadLoader.GetLoaderList()
.Where(l => (l.name ?? "") == mcDownloadClientLibName || (l.name ?? "") == mcDownloadClientJsonName)
.Where(l => l.State != ModBase.LoadState.Finished).ToList();
if (targetLoaders.Any())
ModBase.Log("[Download] OptiFine 安装正在等待原版文件下载完成");
while (targetLoaders.Any() && !task.IsAborted)
{
targetLoaders = targetLoaders.Where(l => l.State != ModBase.LoadState.Finished).ToList();
Thread.Sleep(50);
}
if (task.IsAborted)
return;
// 拷贝原版文件
if (!isCustomFolder)
return;
lock (vanillaSyncLock)
{
var clientName = ModBase.GetFolderNameFromPath(clientFolder);
Directory.CreateDirectory(Path.Combine(mcFolder, "versions", downloadInfo.Inherit));
if (!File.Exists(Path.Combine(mcFolder, "versions", downloadInfo.Inherit, downloadInfo.Inherit + ".json")))
ModBase.CopyFile($"{clientFolder}{clientName}.json",
$@"{mcFolder}versions\{downloadInfo.Inherit}\{downloadInfo.Inherit}.json");
if (!File.Exists(Path.Combine(mcFolder, "versions", downloadInfo.Inherit, downloadInfo.Inherit + ".jar")))
ModBase.CopyFile($"{clientFolder}{clientName}.jar",
$@"{mcFolder}versions\{downloadInfo.Inherit}\{downloadInfo.Inherit}.jar");
}
})
{
ProgressWeight = 0.1d,
show = false
});
// 安装(新旧方式均需要原版 Jar 和 Json)
if (isNewVersion)
{
ModBase.Log("[Download] 检测为新版 OptiFine" + downloadInfo.Inherit);
loaders.Add(new ModLoader.LoaderTask<List<DownloadFile>, bool>(
Lang.Text("Minecraft.Download.Stage.InstallOptiFine.MethodA"), task =>
{
var baseMcFolderHome = ModMain.RequestTaskTempFolder();
var baseMcFolder = Path.Combine(baseMcFolderHome, ".minecraft");
try
{
// 准备安装环境
if (Directory.Exists(Path.Combine(baseMcFolder, "versions", downloadInfo.Inherit)))
ModBase.DeleteDirectory(Path.Combine(baseMcFolder, "versions", downloadInfo.Inherit));
Directory.CreateDirectory(Path.Combine(baseMcFolder, "versions", downloadInfo.Inherit));
ModFolder.McFolderLauncherProfilesJsonCreate(baseMcFolder);
ModBase.CopyFile(
Path.Combine(mcFolder, "versions", downloadInfo.Inherit, downloadInfo.Inherit + ".json"),
Path.Combine(baseMcFolder, "versions", downloadInfo.Inherit, downloadInfo.Inherit + ".json"));
ModBase.CopyFile(
Path.Combine(mcFolder, "versions", downloadInfo.Inherit, downloadInfo.Inherit + ".jar"),
Path.Combine(baseMcFolder, "versions", downloadInfo.Inherit, downloadInfo.Inherit + ".jar"));
task.Progress = 0.06d;
// 进行安装
var useJavaWrapper = ModBase.IsUtf8CodePage();
Retry: ;
try
{
McDownloadOptiFineInstall(baseMcFolderHome, target, task, useJavaWrapper, javaVersion);
}
catch (Exception ex)
{
if (!useJavaWrapper)
{
ModBase.Log(ex, "不使用 JavaWrapper 安装 OptiFine 失败,将使用 JavaWrapper 并重试");
useJavaWrapper = true;
goto Retry;
}
throw new Exception(Lang.Text("Minecraft.Download.Error.OptiFineInstallerRunFailed"), ex);
}
task.Progress = 0.96d;
// 复制文件
File.Delete(Path.Combine(baseMcFolder, "launcher_profiles.json"));
ModBase.CopyDirectory(baseMcFolder, mcFolder);
task.Progress = 0.98d;
// 清理文件
File.Delete(target);
ModBase.DeleteDirectory(baseMcFolderHome);
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.OptiFineInstallFailed.MethodA"), ex);
}
})
{
ProgressWeight = 8d
});
}
else
{
ModBase.Log("[Download] 检测为旧版 OptiFine" + downloadInfo.Inherit);
// 新建实例文件夹
// 复制 Jar 文件
// 建立 Json 文件
loaders.Add(new ModLoader.LoaderTask<List<DownloadFile>, bool>(
Lang.Text("Minecraft.Download.Stage.InstallOptiFine.MethodB"), task =>
{
try
{
Directory.CreateDirectory(versionFolder);
task.Progress = 0.1d;
if (File.Exists(Path.Combine(versionFolder, id + ".jar"))) File.Delete(Path.Combine(versionFolder, id + ".jar"));
ModBase.CopyFile(
Path.Combine(mcFolder, "versions", downloadInfo.Inherit, downloadInfo.Inherit + ".jar"),
Path.Combine(versionFolder, id + ".jar"));
task.Progress = 0.7d;
var inheritInstance =
new McInstance(Path.Combine(mcFolder, "versions", downloadInfo.Inherit));
var json = @"{
""id"": """ + id + @""",
""inheritsFrom"": """ + downloadInfo.Inherit + @""",
""time"": """ +
(string.IsNullOrEmpty(downloadInfo.ReleaseTime)
? inheritInstance.releaseTime.ToString("yyyy'-'MM'-'dd", CultureInfo.InvariantCulture)
: downloadInfo.ReleaseTime.Replace("/", "-")) + @"T23:33:33+08:00"",
""releaseTime"": """ +
(string.IsNullOrEmpty(downloadInfo.ReleaseTime)
? inheritInstance.releaseTime.ToString("yyyy'-'MM'-'dd", CultureInfo.InvariantCulture)
: downloadInfo.ReleaseTime.Replace("/", "-")) + @"T23:33:33+08:00"",
""type"": ""release"",
""libraries"": [
{""name"": ""optifine:OptiFine:" +
downloadInfo.NameFile.Replace("OptiFine_", "").Replace(".jar", "")
.Replace("preview_", "") + // 输出旧版 Json 格式
@"""},
{""name"": ""net.minecraft:launchwrapper:1.12""}
],
""mainClass"": ""net.minecraft.launchwrapper.Launch"",";
task.Progress = 0.8d;
if (inheritInstance.IsOldJson)
json += @"
""minimumLauncherVersion"": 18,
""minecraftArguments"": """ + inheritInstance.JsonObject["minecraftArguments"] + // 输出新版 Json 格式
@" --tweakClass optifine.OptiFineTweaker""
}";
else
json += @"
""minimumLauncherVersion"": ""21"",
""arguments"": {
""game"": [
""--tweakClass"",
""optifine.OptiFineTweaker""
]
}
}";
ModBase.WriteFile(Path.Combine(versionFolder, id + ".json"), json);
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.OptiFineInstallFailed.MethodB"), ex);
}
})
{ ProgressWeight = 1d });
}
// 下载支持库
if (fixLibrary)
{
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeOptiFineLibraries"),
task => task.output =
ModLibrary.McLibNetFilesFromInstance(new McInstance(versionFolder)))
{ ProgressWeight = 1d, show = false });
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadOptiFineLibraries"),
new List<DownloadFile>())
{ ProgressWeight = 4d });
}
return loaders;
}
/// <summary>
/// 获取保存某个 OptiFine 版本的加载器列表。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadOptiFineSaveLoader(ModDownload.DlOptiFineListEntry downloadInfo,
string targetFolder)
{
var loaders = new List<ModLoader.LoaderBase>();
// 获取下载地址
loaders.Add(new ModLoader.LoaderTask<ModDownload.DlOptiFineListEntry, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.ObtainOptiFineDownloadUrl"),
task =>
{
var sources = new List<string>();
// BMCLAPI 源
var bmclapiInherit = downloadInfo.Inherit;
if (bmclapiInherit == "1.8" || bmclapiInherit == "1.9")
bmclapiInherit += ".0"; // #4281
if (downloadInfo.IsPreview)
sources.Add("https://bmclapi2.bangbang93.com/optifine/" + bmclapiInherit + "/HD_U_" +
downloadInfo.DisplayName.Replace(downloadInfo.Inherit + " ", "").Replace(" ", "/"));
else
sources.Add("https://bmclapi2.bangbang93.com/optifine/" + bmclapiInherit + "/HD_U/" +
downloadInfo.DisplayName.Replace(downloadInfo.Inherit + " ", ""));
// 官方源
string pageData;
try
{
using (var resp = HttpRequest
.Create("https://optifine.net/adloadx?f=" + downloadInfo.NameFile)
.WithHeader("Accept", "text/html")
.WithHeader("Accept-Language", "en-US,en;q=0.5")
.WithHeader("X-Requested-With", "XMLHttpRequest")
.SendAsync().GetAwaiter().GetResult())
{
resp.EnsureSuccessStatusCode();
pageData = resp.AsString();
}
task.Progress = 0.8d;
sources.Add("https://optifine.net/" + pageData.RegexSearch(@"downloadx\?f=[^""']+")[0]);
ModBase.Log("[Download] OptiFine " + downloadInfo.DisplayName + " 官方下载地址:" + sources.Last());
}
catch (Exception ex)
{
ModBase.Log(ex, "获取 OptiFine " + downloadInfo.DisplayName + " 官方下载地址失败");
}
task.Progress = 0.9d;
// 构造文件请求
task.output = new List<DownloadFile>
{ new(sources.ToArray(), targetFolder, new ModBase.FileChecker(64 * 1024)) };
})
{
ProgressWeight = 6d
});
// 下载
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadOptiFineMainFile"),
new List<DownloadFile>())
{ ProgressWeight = 10d, block = true });
return loaders;
}
#endregion
#region OptiFine
public static MyListItem OptiFineDownloadListItem(ModDownload.DlOptiFineListEntry entry,
MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
var infoParts = new List<string>
{
entry.IsPreview
? Lang.Text("Download.Version.Type.Preview")
: Lang.Text("Download.Version.Type.Release")
};
if (!string.IsNullOrEmpty(entry.ReleaseTime))
infoParts.Add(Lang.Text("Download.Version.ReleaseDate", entry.ReleaseTime));
if (entry.RequiredForgeVersion is null)
infoParts.Add(Lang.Text("Download.Version.Optifine.IncompatibleForge"));
else if (!string.IsNullOrEmpty(entry.RequiredForgeVersion))
infoParts.Add(Lang.Text("Download.Version.Optifine.CompatibleForge", entry.RequiredForgeVersion));
var newItem = new MyListItem
{
Title = entry.DisplayName,
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = string.Join(" | ", infoParts),
Logo = ModBase.pathImage + "Blocks/GrassPath.png"
};
newItem.Click += onClick;
// 建立菜单
newItem.ContentHandler = isSaveOnly
? OptiFineSaveContMenuBuild
: OptiFineContMenuBuild;
// 结束
return newItem;
}
private static void OptiFineSaveContMenuBuild(object sender, EventArgs e)
{
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (sender, e) => OptiFineLog_Click(sender, (RoutedEventArgs)e);
((dynamic)sender).Buttons = new[] { btnInfo };
}
private static void OptiFineContMenuBuild(object sender, EventArgs e)
{
var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveAs") };
ToolTipService.SetPlacement(btnSave, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnSave, 30d);
ToolTipService.SetHorizontalOffset(btnSave, 2d);
//btnSave.Click += () ModDownloadLib.OptiFineSave_Click;
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (sender, e) => OptiFineLog_Click(sender, (RoutedEventArgs)e);
((dynamic)sender).Buttons = new[] { btnSave, btnInfo };
}
private static void OptiFineLog_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlOptiFineListEntry version;
if (((dynamic)sender).Tag is not null)
version = (ModDownload.DlOptiFineListEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
version = (ModDownload.DlOptiFineListEntry)((dynamic)sender).Parent.Tag;
else
version = (ModDownload.DlOptiFineListEntry)((dynamic)sender).Parent.Parent.Tag;
ModBase.OpenWebsite("https://optifine.net/changelog?f=" + version.NameFile);
}
public static void OptiFineSave_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlOptiFineListEntry version;
if (((dynamic)sender).Tag is not null)
version = (ModDownload.DlOptiFineListEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
version = (ModDownload.DlOptiFineListEntry)((dynamic)sender).Parent.Tag;
else
version = (ModDownload.DlOptiFineListEntry)((dynamic)sender).Parent.Parent.Tag;
McDownloadOptiFineSave(version);
}
#endregion
#region LiteLoader
public static void McDownloadLiteLoader(ModDownload.DlLiteLoaderListEntry downloadInfo)
{
try
{
var id = downloadInfo.Inherit;
var target = Path.Combine(ModBase.pathTemp, "Download", id + "-Liteloader.jar");
var versionName = downloadInfo.Inherit + "-LiteLoader";
var versionFolder = Path.Combine(ModFolder.mcFolderSelected, "versions", versionName);
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") != (Lang.Text("Minecraft.Download.Stage.LiteLoaderDownload", id) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 已有实例检查
if (File.Exists(Path.Combine(versionFolder, versionName + ".json")))
{
if (ModMain.MyMsgBox(
Lang.Text("Minecraft.Download.Error.InstanceAlreadyExists", versionName, "\r\n"),
Lang.Text("Minecraft.Download.Error.InstanceAlreadyExists.Title"),
Lang.Text("Common.Action.Continue"), Lang.Text("Common.Action.Cancel")
) == 1)
{
File.Delete(Path.Combine(versionFolder, versionName + ".jar"));
File.Delete(Path.Combine(versionFolder, versionName + ".json"));
}
else
{
return;
}
}
// 启动
var loader =
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.LiteLoaderDownload", id),
McDownloadLiteLoaderLoader(downloadInfo))
{ OnStateChanged = McInstallState };
loader.Start(versionFolder);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 LiteLoader 下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
private static void McDownloadLiteLoaderSave(ModDownload.DlLiteLoaderListEntry downloadInfo)
{
try
{
var id = downloadInfo.Inherit;
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Version.SelectSaveLocation"), downloadInfo.FileName.Replace("-SNAPSHOT", ""),
Lang.Text("Minecraft.Download.Stage.ForgelikeInstallerFilter", "LiteLoader", "jar"));
if (!target.Contains(@"\"))
return;
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") != (Lang.Text("Minecraft.Download.Stage.LiteLoaderDownload", id) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 构造步骤加载器
var loaders = new List<ModLoader.LoaderBase>();
// 下载
var address = new List<string>();
if (downloadInfo.IsLegacy)
// 老版本
switch (downloadInfo.Inherit ?? "")
{
case "1.7.10":
{
address.Add("https://dl.liteloader.com/redist/1.7.10/liteloader-installer-1.7.10-04.jar");
break;
}
case "1.7.2":
{
address.Add("https://dl.liteloader.com/redist/1.7.2/liteloader-installer-1.7.2-04.jar");
break;
}
case "1.6.4":
{
address.Add("https://dl.liteloader.com/redist/1.6.4/liteloader-installer-1.6.4-01.jar");
break;
}
case "1.6.2":
{
address.Add("https://dl.liteloader.com/redist/1.6.2/liteloader-installer-1.6.2-04.jar");
break;
}
case "1.5.2":
{
address.Add("https://dl.liteloader.com/redist/1.5.2/liteloader-installer-1.5.2-01.jar");
break;
}
default:
{
throw new NotSupportedException(Lang.Text("Minecraft.Download.Error.UnknownMinecraftVersion",
downloadInfo.Inherit));
}
}
else
// 官方源
address.Add("http://jenkins.liteloader.com/job/LiteLoaderInstaller%20" + downloadInfo.Inherit +
"/lastSuccessfulBuild/artifact/" +
(downloadInfo.Inherit == "1.8" ? "ant/dist/" : "build/libs/") + downloadInfo.FileName);
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadMainFile"),
new List<DownloadFile> { new(address.ToArray(), target, new ModBase.FileChecker(1024 * 1024)) })
{ ProgressWeight = 15d });
// 启动
var loader =
new ModLoader.LoaderCombo<ModDownload.DlLiteLoaderListEntry>(
Lang.Text("Minecraft.Download.Stage.LiteLoaderInstallerDownload", id), loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(downloadInfo);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 LiteLoader 安装器下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
/// <summary>
/// 获取下载某个 LiteLoader 实例的加载器列表。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadLiteLoaderLoader(ModDownload.DlLiteLoaderListEntry downloadInfo,
string mcFolder = null, ModLoader.LoaderCombo<string> clientDownloadLoader = null, bool fixLibrary = true)
{
// 参数初始化
mcFolder = mcFolder ?? ModFolder.mcFolderSelected;
var isCustomFolder = (mcFolder ?? "") != (ModFolder.mcFolderSelected ?? "");
var id = downloadInfo.Inherit;
var target = Path.Combine(ModBase.pathTemp, "Download", id + "-Liteloader.jar");
var versionName = downloadInfo.Inherit + "-LiteLoader";
var versionFolder = Path.Combine(mcFolder, "versions", versionName);
var loaders = new List<ModLoader.LoaderBase>();
// 启动依赖实例的下载
if (clientDownloadLoader is null)
loaders.Add(new ModLoader.LoaderTask<string, string>(
Lang.Text("Minecraft.Download.Stage.StartLiteLoaderDependencyDownload"), _ =>
{
if (isCustomFolder)
throw new Exception(Lang.Text("Minecraft.Download.Error.NoVanillaLoaderSpecified"));
clientDownloadLoader = McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading,
downloadInfo.Inherit);
})
{
ProgressWeight = 0.2d,
show = false,
block = false
});
// 安装
// 新建实例文件夹
// 构造实例 Json
// 输出 Json 文件
loaders.Add(new ModLoader.LoaderTask<string, string>(Lang.Text("Minecraft.Download.Stage.InstallLiteLoader"),
_ =>
{
try
{
Directory.CreateDirectory(versionFolder);
var versionJson = new JsonObject();
versionJson.Add("id", versionName);
var releaseDate = DateTime.Parse(downloadInfo.ReleaseTime, Lang.Culture);
versionJson.Add("time", releaseDate);
versionJson.Add("releaseTime", releaseDate);
versionJson.Add("type", "release");
versionJson.Add("arguments",
(JsonNode)ModBase.GetJson("{\"game\":[\"--tweakClass\",\"" + downloadInfo.jsonToken["tweakClass"] +
"\"]}"));
versionJson.Add("libraries", downloadInfo.jsonToken["libraries"]?.DeepClone());
versionJson["libraries"].AsArray().Add(ModBase.GetJson("{\"name\": \"com.mumfrey:liteloader:" +
downloadInfo.jsonToken["version"] +
"\",\"url\": \"https://dl.liteloader.com/versions/\"}"));
versionJson.Add("mainClass", "net.minecraft.launchwrapper.Launch");
versionJson.Add("minimumLauncherVersion", 18);
versionJson.Add("inheritsFrom", downloadInfo.Inherit);
versionJson.Add("jar", downloadInfo.Inherit);
ModBase.WriteFile(Path.Combine(versionFolder, versionName + ".json"), versionJson.ToString());
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.LiteLoaderInstallFailed"), ex);
}
}) { ProgressWeight = 1d });
// 下载支持库
if (fixLibrary)
{
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeLiteLoaderLibraries"),
task => task.output =
ModLibrary.McLibNetFilesFromInstance(new McInstance(versionFolder)))
{ ProgressWeight = 1d, show = false });
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLiteLoaderLibraries"),
new List<DownloadFile>())
{ ProgressWeight = 6d });
}
return loaders;
}
#endregion
#region LiteLoader
public static MyListItem LiteLoaderDownloadListItem(ModDownload.DlLiteLoaderListEntry entry,
MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
var infoParts = new List<string>
{
entry.IsPreview
? Lang.Text("Download.Version.Type.Preview")
: Lang.Text("Download.Version.Type.Stable")
};
if (!string.IsNullOrEmpty(entry.ReleaseTime))
infoParts.Add(Lang.Text("Download.Version.ReleaseDate", entry.ReleaseTime));
// 建立控件
var newItem = new MyListItem
{
Title = entry.Inherit,
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = string.Join(" | ", infoParts),
Logo = ModBase.pathImage + "Blocks/Egg.png"
};
newItem.Click += onClick;
// 建立菜单
newItem.ContentHandler = isSaveOnly
? LiteLoaderSaveContMenuBuild
: LiteLoaderContMenuBuild;
// 结束
return newItem;
}
private static void LiteLoaderSaveContMenuBuild(MyListItem sender, EventArgs e)
{
if ((bool)((dynamic)sender.Tag).IsLegacy)
{
sender.Buttons = Array.Empty<MyIconButton>();
}
else
{
var btnList = new MyIconButton { SvgIcon = "lucide/list", ToolTip = Lang.Text("Download.Version.ViewAllVersions"), Tag = sender };
ToolTipService.SetPlacement(btnList, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnList, 30d);
ToolTipService.SetHorizontalOffset(btnList, 2d);
btnList.Click += (sender, e) => LiteLoaderAll_Click(sender, (RoutedEventArgs)e);
sender.Buttons = new[] { btnList };
}
}
private static void LiteLoaderContMenuBuild(MyListItem sender, EventArgs e)
{
var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveInstaller"), Tag = sender };
ToolTipService.SetPlacement(btnSave, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnSave, 30d);
ToolTipService.SetHorizontalOffset(btnSave, 2d);
btnSave.Click += (sender, e) => LiteLoaderSave_Click(sender, (RoutedEventArgs)e);
if ((bool)((dynamic)sender.Tag).IsLegacy)
{
sender.Buttons = [btnSave];
}
else
{
var btnList = new MyIconButton { SvgIcon = "lucide/list", ToolTip = Lang.Text("Download.Version.ViewAllVersions"), Tag = sender };
ToolTipService.SetPlacement(btnList, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnList, 30d);
ToolTipService.SetHorizontalOffset(btnList, 2d);
btnList.Click += (sender, e) => LiteLoaderAll_Click(sender, (RoutedEventArgs)e);
sender.Buttons = [btnSave, btnList];
}
}
private static void LiteLoaderAll_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlLiteLoaderListEntry version;
if (((dynamic)sender).Tag is ModDownload.DlLiteLoaderListEntry)
version = (ModDownload.DlLiteLoaderListEntry)((dynamic)sender).Tag;
else
version = (ModDownload.DlLiteLoaderListEntry)((dynamic)sender).Tag.Tag;
ModBase.OpenWebsite("https://jenkins.liteloader.com/view/" + version.Inherit);
}
public static void LiteLoaderSave_Click(object sender, RoutedEventArgs e)
{
// ListItem 与小按钮都会调用这个方法
ModDownload.DlLiteLoaderListEntry version;
if (((dynamic)sender).Tag is ModDownload.DlLiteLoaderListEntry)
version = (ModDownload.DlLiteLoaderListEntry)((dynamic)sender).Tag;
else
version = (ModDownload.DlLiteLoaderListEntry)((dynamic)sender).Tag.Tag;
McDownloadLiteLoaderSave(version);
}
#endregion
#region Forgelike
public static void McDownloadForgelikeSave(ModDownload.DlForgelikeEntry info)
{
try
{
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Version.SelectSaveLocation"),
$"{info.LoaderName}-{info.Inherit}-{info.VersionName}.{info.FileExtension}",
Lang.Text("Minecraft.Download.Stage.ForgelikeInstallerFilter", info.LoaderName, info.FileExtension));
var displayName = $"{info.LoaderName} {info.Inherit} - {info.VersionName}";
if (!target.Contains(@"\"))
return;
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.ForgelikeDownload", displayName) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 获取下载地址
var files = new List<DownloadFile>();
if (info.forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.NeoForge)
{
// NeoForge
var neo = (ModDownload.DlNeoForgeListEntry)info;
var url = neo.UrlBase + "-installer.jar";
files.Add(new DownloadFile(
new[] { url.Replace("maven.neoforged.net/releases", "bmclapi2.bangbang93.com/maven"), url }, target,
new ModBase.FileChecker(64 * 1024)));
}
else if (info.forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.Cleanroom)
{
// Cleanroom
var clr = (ModDownload.DlCleanroomListEntry)info;
var url = clr.UrlBase + "-installer.jar";
files.Add(new DownloadFile(new[] { url }, target, new ModBase.FileChecker(64 * 1024)));
}
else
{
// Forge
var forge = (ModDownload.DlForgeVersionEntry)info;
files.Add(new DownloadFile(
new[]
{
$"https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/{forge.Inherit}-{forge.FileVersion}/forge-{forge.Inherit}-{forge.FileVersion}-{forge.Category}.{forge.FileExtension}",
$"https://files.minecraftforge.net/maven/net/minecraftforge/forge/{forge.Inherit}-{forge.FileVersion}/forge-{forge.Inherit}-{forge.FileVersion}-{forge.Category}.{forge.FileExtension}"
}, target, new ModBase.FileChecker(64 * 1024, hash: forge.Hash)));
}
// 构造加载器
var loaders = new List<ModLoader.LoaderBase>();
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadMainFile"), files)
{ ProgressWeight = 6d });
// 启动
var loader =
new ModLoader.LoaderCombo<ModDownload.DlForgelikeEntry>(
Lang.Text("Minecraft.Download.Stage.ForgelikeDownload", displayName), loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(info);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
$"开始 {info.LoaderName} 安装器下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
private static void ForgelikeInjector(string target, ModLoader.LoaderTask<bool, bool> task, string mcFolder,
bool useJavaWrapper, ModDownload.DlForgelikeEntry.ForgelikeType forgeType)
{
// 选择 Java
JavaEntry java;
lock (ModJava.javaLock)
{
java = ModJava.JavaSelect(Lang.Text("Minecraft.Download.Error.InstallationCanceled"),
new Version(1, 8, 0, 60));
if (java is null)
{
if (!ModJava.JavaDownloadConfirm(Lang.Text("Minecraft.Download.Error.JavaVersionRequired")))
throw new Exception(Lang.Text("Minecraft.Download.Error.JavaNotFoundInstallCanceled"));
// 开始自动下载
var javaLoader = ModJava.GetJavaDownloadLoader();
try
{
javaLoader.Start(17, true);
while (javaLoader.State == ModBase.LoadState.Loading && !task.IsAborted)
Thread.Sleep(10);
}
finally
{
javaLoader.Abort(); // 确保取消时中止 Java 下载
}
// 检查下载结果
java = ModJava.JavaSelect(Lang.Text("Minecraft.Download.Error.InstallationCanceled"),
new Version(1, 8, 0, 60));
if (task.IsAborted)
return;
if (java is null)
throw new Exception(Lang.Text("Minecraft.Download.Error.JavaNotFoundInstallCanceled"));
}
}
// 添加 Java Wrapper 作为主 Jar
string arguments;
if (useJavaWrapper && !Config.Launch.DisableJlw)
arguments =
$@"-Doolloo.jlw.tmpdir=""{ModBase.pathPure.TrimEnd('\\')}"" -cp ""{ModBase.pathTemp}Cache\forge_installer.jar;{target}"" -jar ""{ModLaunch.ExtractJavaWrapper()}"" com.bangbang93.ForgeInstaller ""{mcFolder}";
else
arguments =
$@"-cp ""{ModBase.pathTemp}Cache\forge_installer.jar;{target}"" com.bangbang93.ForgeInstaller ""{mcFolder}";
if (java.Installation.MajorVersion >= 9)
arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " + arguments;
// 开始启动
lock (installSyncLock)
{
var info = new ProcessStartInfo
{
FileName = java.Installation.JavaExePath,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardError = true,
RedirectStandardOutput = true
};
string loaderName = ModBase.GetStringFromEnum(forgeType);
ModBase.Log($"[Download] 开始安装 {loaderName}" + arguments);
var process = new Process { StartInfo = info };
var lastResults = new Queue<string>();
using (var outputWaitHandle = new AutoResetEvent(false))
{
using (var errorWaitHandle = new AutoResetEvent(false))
{
process.OutputDataReceived += (sender, e) =>
{
try
{
if (e.Data is null)
{
outputWaitHandle.Set();
}
else
{
lastResults.Enqueue(e.Data);
if (lastResults.Count > 100)
lastResults.Dequeue();
ForgelikeInjectorLine(e.Data, task);
}
}
catch (ObjectDisposedException ex)
{
}
catch (Exception ex)
{
ModBase.Log(ex, $"读取 {loaderName} 安装器信息失败");
}
try
{
if (task.State == ModBase.LoadState.Aborted && !process.HasExited)
{
ModBase.Log($"[Installer] 由于任务取消,已中止 {loaderName} 安装");
process.Kill();
}
}
catch
{
}
};
process.ErrorDataReceived += (sender, e) =>
{
try
{
if (e.Data is null)
{
errorWaitHandle.Set();
}
else
{
lastResults.Enqueue(e.Data);
if (lastResults.Count > 100)
lastResults.Dequeue();
ForgelikeInjectorLine(e.Data, task);
}
}
catch (ObjectDisposedException ex)
{
}
catch (Exception ex)
{
ModBase.Log(ex, $"读取 {loaderName} 安装器错误信息失败");
}
try
{
if (task.State == ModBase.LoadState.Aborted && !process.HasExited)
{
ModBase.Log($"[Installer] 由于任务取消,已中止 {loaderName} 安装");
process.Kill();
}
}
catch
{
}
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
// 等待
while (!process.HasExited)
Thread.Sleep(10);
// 输出
outputWaitHandle.WaitOne(10000);
errorWaitHandle.WaitOne(10000);
process.Dispose();
// 检查是否安装成功:最后 5 行中是否有 true(true 可能在倒数数行,见 #832)
if (lastResults.Reverse().Take(5).Any(l => l == "true"))
return;
ModBase.Log(lastResults.Join("\r\n"));
var lastLines = "";
for (int i = Math.Max(0, lastResults.Count - 5), loopTo = lastResults.Count - 1;
i <= loopTo;
i++) // 最后 5 行
lastLines += "\r\n" + lastResults.ElementAtOrDefault(i);
throw new Exception(Lang.Text("Minecraft.Download.Error.LoaderInstallerFailedLastLine", loaderName,
lastLines));
}
}
}
}
private static void ForgelikeInjectorLine(string content, ModLoader.LoaderTask<bool, bool> task)
{
switch (content ?? "")
{
case "Extracting json":
{
ModBase.Log("[Installer] " + content);
task.Progress = 0.07d;
break;
}
case "Downloading libraries":
{
ModBase.Log("[Installer] " + content);
task.Progress = 0.08d;
break;
}
case " File exists: Checksum validated.":
{
if (ModBase.modeDebug)
ModBase.Log("[Installer] " + content);
task.Progress += 0.003d;
break;
}
case "Building Processors":
{
task.Progress = 0.18d;
break;
}
case "Task: DOWNLOAD_MOJMAPS": // B
{
task.Progress = 0.2d;
break;
}
case "Task: MERGE_MAPPING": // B
{
task.Progress = 0.3d;
break;
}
case "Splitting: ":
{
task.Progress = 0.35d;
break;
}
case "Parameter Annotations": // B
{
task.Progress = 0.4d;
break;
}
case "Processing Complete": // B
{
task.Progress = 0.5d;
break;
}
case "log: null": // new
{
task.Progress = 0.5d;
break;
}
case "Sorting": // new
{
task.Progress = 0.65d;
break;
}
case "Remapping final jar": // A
{
task.Progress = 0.72d;
break;
}
case "Remapping jar... 50%": // A
{
task.Progress = 0.76d;
break;
}
case "Remapping jar... 100%": // A
{
task.Progress = 0.81d;
break;
}
case "Injecting profile":
{
task.Progress = 0.91d;
break;
}
default:
{
if (ModBase.modeDebug)
ModBase.Log("[Installer] " + content);
return;
}
}
ModBase.Log("[Installer] " + content);
}
/// <summary>
/// 获取下载某个 Forgelike 实例的加载器列表。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadForgelikeLoader(ModDownload.DlForgelikeEntry.ForgelikeType forgeType, string loaderVersion,
string targetVersion, string inherit, ModDownload.DlForgelikeEntry info = null, string mcFolder = null, ModLoader.LoaderCombo<string> clientDownloadLoader = null, string clientFolder = null)
{
// 参数初始化
mcFolder = mcFolder ?? ModFolder.mcFolderSelected;
if (forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.NeoForge && info is null)
{
// 需要传入 API Name,但整合包版本可能不以 1.20.1- 开头,所以需要进行特别处理
if (inherit == "1.20.1" && !loaderVersion.StartsWithF("1.20.1-"))
info = new ModDownload.DlNeoForgeListEntry("1.20.1-" + loaderVersion);
else
info = new ModDownload.DlNeoForgeListEntry(loaderVersion);
}
if (forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.Cleanroom && info is null) info = new ModDownload.DlCleanroomListEntry(loaderVersion);
if (forgeType != ModDownload.DlForgelikeEntry.ForgelikeType.NeoForge && loaderVersion.StartsWithF("1.") && loaderVersion.Contains("-"))
{
// 类似 1.19.3-41.2.8 格式,优先使用 Version 中要求的版本而非 Inherit(例如 1.19.3 却使用了 1.19 的 Forge
inherit = loaderVersion.BeforeFirst("-");
loaderVersion = loaderVersion.AfterLast("-");
}
string loaderName = ModBase.GetStringFromEnum(forgeType);
var isCustomFolder = (mcFolder ?? "") != (ModFolder.mcFolderSelected ?? "");
var installerAddress = ModMain.RequestTaskTempFolder() + "forge_installer.jar";
var versionFolder = $@"{mcFolder}versions\{targetVersion}\";
var displayName = $"{loaderName} {inherit} - {loaderVersion}";
var loaders = new List<ModLoader.LoaderBase>();
var libVersionFolder = $@"{ModFolder.mcFolderSelected}versions\{targetVersion}\"; // 作为 Lib 文件目标的实例文件夹
// 获取 Forge 下载信息
if (info is null)
loaders.Add(new ModLoader.LoaderTask<string, string>(
Lang.Text("Minecraft.Download.Stage.ObtainLoaderDetails", loaderName), task =>
{
// 获取 Forge 对应 MC 版本列表
var forgeLoader =
new ModLoader.LoaderTask<string, List<ModDownload.DlForgeVersionEntry>>(
"McDownloadForgeLoader " + inherit, ModDownload.DlForgeVersionMain);
forgeLoader.WaitForExit(inherit);
task.Progress = 0.8d;
// 查找对应版本
foreach (var ForgeVersion in forgeLoader.output)
if (McVersionComparer.CompareVersion(ForgeVersion.version.ToString(), loaderVersion) == 0)
{
info = ForgeVersion;
return;
}
throw new Exception(Lang.Text("Minecraft.Download.Error.LoaderDetailsNotFound", loaderName, inherit,
loaderVersion));
})
{
ProgressWeight = 3d
});
// 下载 Forgelike 主文件
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.PrepareLoaderDownload", loaderName), task =>
{
// 启动依赖实例的下载
if (clientDownloadLoader is null)
{
if (isCustomFolder)
throw new Exception(Lang.Text("Minecraft.Download.Error.NoVanillaLoaderSpecified"));
clientDownloadLoader =
McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading, inherit);
}
// 添加主文件下载
var files = new List<DownloadFile>();
if (info.forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.NeoForge)
{
// NeoForge
var neo = (ModDownload.DlNeoForgeListEntry)info;
var url = neo.UrlBase + "-installer.jar";
files.Add(new DownloadFile(
new[] { url.Replace("maven.neoforged.net/releases", "bmclapi2.bangbang93.com/maven"), url },
installerAddress, new ModBase.FileChecker(64 * 1024)));
}
else if (info.forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.Cleanroom)
{
// Cleanroom
var clr = (ModDownload.DlCleanroomListEntry)info;
var url = clr.UrlBase + "-installer.jar";
files.Add(new DownloadFile(new[] { url }, installerAddress, new ModBase.FileChecker(64 * 1024)));
}
else
{
// Forge
var forge = (ModDownload.DlForgeVersionEntry)info;
var fileName =
$"{forge.Inherit.Replace("-", "_")}-{forge.FileVersion}/forge-{forge.Inherit.Replace("-", "_")}-{forge.FileVersion}-{forge.Category}.{forge.FileExtension}";
files.Add(new DownloadFile(
new[]
{
$"https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/{fileName}",
$"https://files.minecraftforge.net/maven/net/minecraftforge/forge/{fileName}"
}, installerAddress, new ModBase.FileChecker(64 * 1024, hash: forge.Hash)));
}
task.output = files;
})
{
ProgressWeight = 0.5d,
show = false
});
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLoaderMainFile", loaderName),
new List<DownloadFile>())
{ ProgressWeight = 9d });
// 安装(仅在新版安装时需要原版 Jar)
if (forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.NeoForge || Convert.ToDouble(loaderVersion.BeforeFirst(".")) >= 20d)
{
ModBase.Log($"[Download] 检测为{(forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.Forge ? " Forge" : " " + forgeType)}" + loaderVersion);
List<ModLibrary.McLibToken> libs = null;
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeLoaderLibraries", loaderName), task =>
{
task.output = new List<DownloadFile>();
ZipArchive installer = null;
try
{
// 解压并获取、合并两个 Json 的信息
ModBase.WaitForFileReady(installerAddress);
installer = new ZipArchive(new FileStream(installerAddress, FileMode.Open));
task.Progress = 0.2d;
var json = (JsonObject)ModBase.GetJson(
ModBase.ReadFile(installer.GetEntry("install_profile.json").Open()));
var json2 = (JsonObject)ModBase.GetJson(ModBase.ReadFile(installer.GetEntry("version.json").Open()));
json.Merge(json2);
// 如果是 1.16.5 就升级一下 Authlib
if (inherit == "1.16.5" && (bool)Config.Download.FixAuthLib)
json = (JsonObject)ModBase.GetJson(json.ToString()
.Replace("2.1.28/authlib-2.1.28.jar", "2.3.31/authlib-2.3.31.jar")
.Replace("com.mojang:authlib:2.1.28", "com.mojang:authlib:2.3.31")
.Replace("ad54da276bf59983d02d5ed16fc14541354c71fd",
"bbd00ca33b052f73a6312254780fc580d2da3535").Replace("76328", "87662"));
// 获取 Lib 下载信息
libs = ModLibrary.McLibListGetWithJson(json, true);
// 添加 Mappings 下载信息
if (json["data"] is not null && json["data"]["MOJMAPS"] is not null)
{
// 下载原版 Json 文件
task.Progress = 0.4d;
var rawJson = (JsonObject)ModBase.GetJson(ModNet.NetGetCodeByLoader(
ModDownload.DlSourceLauncherOrMetaGet(
ModDownload.DlClientListGet(inherit)?.ToString()), isJson: true));
// [net.minecraft:client:1.17.1-20210706.113038:mappings@txt] 或 @tsrg]
var originalName = json["data"]["MOJMAPS"]["client"].ToString().Trim("[]".ToCharArray())
.BeforeFirst("@");
var address = ModLibrary.McLibGet(originalName).Replace(".jar",
"-mappings." + json["data"]["MOJMAPS"]["client"].ToString().Trim("[]".ToCharArray())
.Split("@")[1]);
var clientMappings = rawJson["downloads"]["client_mappings"];
libs.Add(new ModLibrary.McLibToken
{
IsNatives = false,
LocalPath = address,
OriginalName = originalName,
Url = (string)clientMappings["url"],
size = (long)clientMappings["size"],
Sha1 = (string)clientMappings["sha1"]
});
ModBase.Log(
$"[Download] 需要下载 Mappings{clientMappings["url"]} (SHA1: {clientMappings["sha1"]})");
}
task.Progress = 0.8d;
// 去除其中的原始 Forgelike 项
for (int i = 0, loopTo = libs.Count - 1; i <= loopTo; i++)
if (libs[i].LocalPath.EndsWithF($"{loaderName.ToLower()}-{inherit}-{loaderVersion}.jar") ||
libs[i].LocalPath.EndsWithF($"{loaderName.ToLower()}-{inherit}-{loaderVersion}-client.jar"))
{
ModBase.Log($"[Download] 已从待下载 {loaderName} 支持库中移除:" + libs[i].LocalPath,
ModBase.LogLevel.Debug);
libs.RemoveAt(i);
break;
}
task.output = ModLibrary.McLibNetFilesFromTokens(libs);
}
catch (Exception ex)
{
throw new Exception(
Lang.Text("Minecraft.Download.Error.LoaderLibraryListFailed",
forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.Forge
? Lang.Text("Minecraft.Download.Loader.NewForge")
: " " + forgeType), ex);
}
finally
{
// 释放文件
if (installer is not null)
installer.Dispose();
}
})
{
ProgressWeight = 2d
});
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLoaderLibraries", loaderName),
new List<DownloadFile>())
{ ProgressWeight = 12d });
loaders.Add(new ModLoader.LoaderTask<List<DownloadFile>, bool>(
Lang.Text("Minecraft.Download.Stage.GetLoaderLibraries", loaderName), task =>
{
#region Forgelike
if (isCustomFolder)
foreach (var LibFile in libs)
{
var realPath = LibFile.LocalPath.Replace(ModFolder.mcFolderSelected, mcFolder);
if (!File.Exists(realPath))
{
Directory.CreateDirectory(Path.GetDirectoryName(realPath));
ModBase.CopyFile(LibFile.LocalPath, realPath);
}
if (ModBase.modeDebug)
ModBase.Log($"[Download] 复制的 {loaderName} 支持库文件:" + LibFile.LocalPath);
}
#endregion
#region
// 等待原版文件下载完成
if (clientDownloadLoader is null)
return;
var targetLoaders = clientDownloadLoader.GetLoaderList()
.Where(l => (l.name ?? "") == mcDownloadClientLibName || (l.name ?? "") == mcDownloadClientJsonName)
.Where(l => l.State != ModBase.LoadState.Finished).ToList();
if (targetLoaders.Any())
ModBase.Log($"[Download] {loaderName} 安装正在等待原版文件下载完成");
while (targetLoaders.Any() && !task.IsAborted)
{
targetLoaders = targetLoaders.Where(l => l.State != ModBase.LoadState.Finished).ToList();
Thread.Sleep(50);
}
if (task.IsAborted)
return;
// 拷贝原版文件
if (!isCustomFolder)
return;
lock (vanillaSyncLock)
{
var clientName = ModBase.GetFolderNameFromPath(clientFolder);
Directory.CreateDirectory(Path.Combine(mcFolder, "versions", inherit));
if (!File.Exists(Path.Combine(mcFolder, "versions", inherit, inherit + ".json")))
ModBase.CopyFile(Path.Combine(clientFolder, clientName + ".json"),
Path.Combine(mcFolder, "versions", inherit, inherit + ".json"));
if (!File.Exists(Path.Combine(mcFolder, "versions", inherit, inherit + ".jar")))
ModBase.CopyFile(Path.Combine(clientFolder, clientName + ".jar"),
Path.Combine(mcFolder, "versions", inherit, inherit + ".jar"));
}
#endregion
})
{
ProgressWeight = 0.1d,
show = false
});
loaders.Add(new ModLoader.LoaderTask<bool, bool>(
forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.Forge
? Lang.Text("Minecraft.Download.Stage.InstallForge.MethodA")
: Lang.Text("Minecraft.Download.Stage.InstallForgeType", forgeType), task =>
{
ModBase.WaitForFileReady(installerAddress);
var installer = new ZipArchive(new FileStream(installerAddress, FileMode.Open));
try
{
// 记录当前文件夹列表(在新建目标文件夹之前)
ModBase.Log("[Download] 开始进行 Forgelike 安装:" + installerAddress);
// 解压并获取信息
var oldList = new DirectoryInfo(mcFolder + "versions/")
.EnumerateDirectories().Select(i => i.FullName).ToList();
// 新建目标实例文件夹
var json = ModBase.GetJson(ModBase.ReadFile(installer.GetEntry("install_profile.json").Open()));
Directory.CreateDirectory(versionFolder);
task.Progress = 0.04d;
// 释放 launcher_installer.json
ModFolder.McFolderLauncherProfilesJsonCreate(mcFolder);
task.Progress = 0.05d;
// 运行 Forge 安装器
var useJavaWrapper = ModBase.IsUtf8CodePage();
Retry:
try
{
// 释放 Forge 注入器
ModBase.WriteFile(Path.Combine(ModBase.pathTemp, "Cache", "forge_installer.jar"),
ModBase.GetResourceStream("Resources/forge-installer.jar"));
task.Progress = 0.06d;
// 运行注入器
ForgelikeInjector(installerAddress, task, mcFolder, useJavaWrapper, forgeType);
task.Progress = 0.97d;
}
catch (Exception ex)
{
if (!useJavaWrapper)
{
ModBase.Log(ex, $"不使用 JavaWrapper 安装 {loaderName} 失败,将使用 JavaWrapper 并重试");
useJavaWrapper = true;
goto Retry;
}
throw new Exception(
Lang.Text("Minecraft.Download.Error.LoaderInstallerRunFailed", loaderName), ex);
// 拷贝新增的实例 Json
}
var deltaList = new DirectoryInfo(mcFolder + "versions/").EnumerateDirectories()
.SkipWhile(i => oldList.Contains(i.FullName)).ToList();
if (deltaList.Count > 1)
// 它可能和 OptiFine 安装同时运行,导致增加的文件不止一个(这导致了 #151)
// 也可能是因为 Forge 安装器的 Bug,生成了一个名字错误的文件夹,所以需要检查文件夹是否为空
deltaList = deltaList
.Where(l => l.Name.ContainsF("forge", true) && l.EnumerateFiles().Any())
.ToList();
// 如果没有新增文件夹,那么预测的文件夹名就是正确的
// 如果只新增 1 个文件夹,那么拷贝 Json 文件
if (deltaList.Count == 1)
{
var jsonFile = deltaList[0].EnumerateFiles().First();
ModBase.WriteFile(Path.Combine(versionFolder, targetVersion + ".json"),
ModBase.ReadFile(jsonFile.FullName));
ModBase.Log(
$"[Download] 已拷贝新增的实例 Json 文件:{jsonFile.FullName} -> {versionFolder}{targetVersion}.json");
}
else if (deltaList.Count > 1)
{
// 新增了多个文件夹
//Enumerable.Select<string>((IEnumerable<DirectoryInfo>)DeltaList, d => d.Name).Join(";")
ModBase.Log(
$"[Download] 有多个疑似的新增实例,无法确定:{string.Join(";", deltaList.Select<DirectoryInfo, string>(d => d.Name))}");
}
else
{
// 没有新增文件夹
ModBase.Log("[Download] 未找到新增的实例文件夹");
}
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.LoaderInstallFailed", loaderName), ex);
}
finally
{
// 清理文件
try
{
if (installer is not null)
installer.Dispose();
if (File.Exists(installerAddress))
File.Delete(installerAddress);
}
catch (Exception ex)
{
ModBase.Log(ex, $"安装 {loaderName} 清理文件时出错");
}
}
})
{
ProgressWeight = 10d
});
}
else
{
ModBase.Log("[Download] 检测为非新版 Forge" + loaderVersion);
loaders.Add(new ModLoader.LoaderTask<List<DownloadFile>, bool>(
$"{(forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.Forge ? Lang.Text("Minecraft.Download.Stage.InstallForge.MethodB") : Lang.Text("Minecraft.Download.Stage.InstallForgeType", forgeType))}",
task =>
{
ZipArchive installer = null;
try
{
// 解压并获取信息
ModBase.WaitForFileReady(installerAddress);
installer = new ZipArchive(new FileStream(installerAddress, FileMode.Open));
task.Progress = 0.2d;
var json = (JsonObject)ModBase.GetJson(
ModBase.ReadFile(installer.GetEntry("install_profile.json").Open()));
task.Progress = 0.4d;
// 新建实例文件夹
Directory.CreateDirectory(versionFolder);
task.Progress = 0.5d;
if (json["install"] is null)
{
// 中版:Legacy 方式 1
ModBase.Log("[Download] 开始进行 Forge 安装,Legacy 方式 1" + installerAddress);
// 建立 Json 文件
var jsonVersion = (JsonObject)ModBase.GetJson(
ModBase.ReadFile(installer.GetEntry(json["json"].ToString().TrimStart('/')).Open()));
jsonVersion["id"] = targetVersion;
ModBase.WriteFile(Path.Combine(versionFolder, targetVersion + ".json"), jsonVersion.ToString());
task.Progress = 0.6d;
// 解压支持库文件
installer.Dispose();
var unrarDir = Path.Combine(Path.GetDirectoryName(installerAddress), "_unrar");
ModBase.ExtractFile(installerAddress, unrarDir);
ModBase.CopyDirectory(Path.Combine(unrarDir, "maven"), Path.Combine(mcFolder, "libraries"));
ModBase.DeleteDirectory(unrarDir);
}
else
{
// 旧版:Legacy 方式 2
ModBase.Log("[Download] 开始进行 Forge 安装,Legacy 方式 2" + installerAddress);
// 解压 Jar 文件
var jarAddress = ModLibrary.McLibGet((string)json["install"]["path"],
customMcFolder: mcFolder);
if (File.Exists(jarAddress))
File.Delete(jarAddress);
ModBase.WriteFile(jarAddress,
installer.GetEntry((string)json["install"]["filePath"]).Open());
task.Progress = 0.9d;
// 建立 Json 文件
json["versionInfo"]["id"] = targetVersion;
if (json["versionInfo"]["inheritsFrom"] is null)
((JsonObject)json["versionInfo"]).Add("inheritsFrom", inherit);
ModBase.WriteFile(Path.Combine(versionFolder, targetVersion + ".json"), json["versionInfo"].ToString());
}
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.ForgeOldInstallFailed"), ex);
}
finally
{
try
{
// 清理文件
if (installer is not null)
installer.Dispose();
if (File.Exists(installerAddress))
File.Delete(installerAddress);
var unrarDir = Path.Combine(Path.GetDirectoryName(installerAddress), "_unrar");
if (Directory.Exists(unrarDir))
ModBase.DeleteDirectory(unrarDir);
}
catch (Exception ex)
{
ModBase.Log(ex, "非新版方式安装 Forge 清理文件时出错");
}
}
})
{
ProgressWeight = 1d
});
}
return loaders;
}
#endregion
#region Forge
public static void ForgeDownloadListItemPreload(StackPanel stack, List<ModDownload.DlForgeVersionEntry> entries,
MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
// 如果只有一个版本,则不特别列出
if (entries.Count == 1)
return;
// 获取推荐版本与最新版本
ModDownload.DlForgeVersionEntry freshVersion = null;
if (entries.Any())
freshVersion = entries[0];
else
ModBase.Log("[System] 未找到可用的 Forge 版本", ModBase.LogLevel.Debug);
ModDownload.DlForgeVersionEntry recommendedVersion = null;
foreach (var Entry in entries)
if (Entry.IsRecommended)
recommendedVersion = Entry;
// 若推荐版本与最新版本为同一版本,则仅显示推荐版本
if (freshVersion is not null && ReferenceEquals(freshVersion, recommendedVersion))
freshVersion = null;
// 显示各个版本
if (recommendedVersion is not null)
{
var recommended = ForgeDownloadListItem(recommendedVersion, onClick, isSaveOnly);
recommended.Info = Lang.Text("Download.Version.Type.Recommended") + (string.IsNullOrEmpty(recommended.Info) ? "" : " | " + recommended.Info);
stack.Children.Add(recommended);
}
if (freshVersion is not null)
{
var fresh = ForgeDownloadListItem(freshVersion, onClick, isSaveOnly);
fresh.Info = Lang.Text("Download.Version.Latest.Title") + (string.IsNullOrEmpty(fresh.Info) ? "" : " | " + fresh.Info);
stack.Children.Add(fresh);
}
// 添加间隔
stack.Children.Add(new TextBlock
{
Text = Lang.Text("Download.Version.AllVersions", entries.Count), HorizontalAlignment = HorizontalAlignment.Left,
Margin = new Thickness(6d, 13d, 0d, 4d)
});
}
public static MyListItem ForgeDownloadListItem(ModDownload.DlForgeVersionEntry entry,
MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
var infoParts = new List<string>();
if (!string.IsNullOrEmpty(entry.ReleaseTime))
infoParts.Add(Lang.Text("Download.Version.ReleaseDate", entry.ReleaseTime));
if (ModBase.modeDebug)
infoParts.Add(Lang.Text("Download.Version.Forge.Type", entry.Category));
// 建立控件
var newItem = new MyListItem
{
Title = entry.VersionName,
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = string.Join(" | ", infoParts),
Logo = ModBase.pathImage + "Blocks/Anvil.png"
};
newItem.Click += onClick;
// 建立菜单
if (isSaveOnly)
newItem.ContentHandler = ForgeSaveContMenuBuild;
else
newItem.ContentHandler = ForgeContMenuBuild;
// 结束
return newItem;
}
private static void ForgeContMenuBuild(MyListItem sender, EventArgs e)
{
var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveAs") };
ToolTipService.SetPlacement(btnSave, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnSave, 30d);
ToolTipService.SetHorizontalOffset(btnSave, 2d);
btnSave.Click += (ss, ee) => ForgeSave_Click(ss, (dynamic)ee);
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (ss, ee) => ForgeLog_Click(ss, (dynamic)ee);
sender.Buttons = new[] { btnSave, btnInfo };
}
private static void ForgeSaveContMenuBuild(MyListItem sender, EventArgs e)
{
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (ss, ee) => ForgeLog_Click(ss, (dynamic)e);
sender.Buttons = new[] { btnInfo };
}
private static void ForgeLog_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlForgeVersionEntry version;
if (((dynamic)sender).Tag is not null)
version = (ModDownload.DlForgeVersionEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
version = (ModDownload.DlForgeVersionEntry)((dynamic)sender).Parent.Tag;
else
version = (ModDownload.DlForgeVersionEntry)((dynamic)sender).Parent.Parent.Tag;
ModBase.OpenWebsite(
$"https://files.minecraftforge.net/maven/net/minecraftforge/forge/{version.Inherit}-{version.VersionName}/forge-{version.Inherit}-{version.VersionName}-changelog.txt");
}
public static void ForgeSave_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlForgeVersionEntry version;
if (((dynamic)sender).Tag is not null)
version = (ModDownload.DlForgeVersionEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
version = (ModDownload.DlForgeVersionEntry)((dynamic)sender).Parent.Tag;
else
version = (ModDownload.DlForgeVersionEntry)((dynamic)sender).Parent.Parent.Tag;
McDownloadForgelikeSave(version);
}
#endregion
#region Forge
/// <summary>
/// 尝试刷新 Forge 推荐版本缓存。
/// </summary>
public static void McDownloadForgeRecommendedRefresh()
{
if (isForgeRecommendedRefreshed)
return;
isForgeRecommendedRefreshed = true;
// 获取所有推荐版本列表
// 内容为:"1.15.2":"31.2.0"
// 保存
ModBase.RunInNewThread(() =>
{
try
{
ModBase.Log("[Download] 刷新 Forge 推荐版本缓存开始");
var result = ModNet.NetGetCodeByLoader("https://bmclapi2.bangbang93.com/forge/promos");
if (result.Length < 1000) throw new Exception(Lang.Text("Minecraft.Download.Error.ForgePromosResultTooShort", result));
var resultJson = (JsonNode)ModBase.GetJson(result);
var recommendedList = new List<string>();
foreach (JsonObject Version in resultJson.AsArray())
{
if (Version["name"] is null || Version["build"] is null) continue;
var name = (string)Version["name"];
if (!name.EndsWithF("-recommended")) continue;
recommendedList.Add("\"" + name.Replace("-recommended",
"\":\"" + Version["build"]["version"] + "\""));
}
if (recommendedList.Count < 5)
throw new Exception(Lang.Text("Minecraft.Download.Error.ForgeRecommendedTooFew", result));
var cacheJson = "{" + recommendedList.Join(",") + "}";
ModBase.WriteFile(Path.Combine(ModBase.pathTemp, "Cache", "ForgeRecommendedList.json"), cacheJson);
ModBase.Log("[Download] 刷新 Forge 推荐版本缓存成功");
}
catch (Exception ex)
{
ModBase.Log(ex, "刷新 Forge 推荐版本缓存失败");
}
}, "ForgeRecommendedRefresh");
}
private static bool isForgeRecommendedRefreshed;
/// <summary>
/// 尝试获取某个 MC 版本对应的 Forge 推荐版本。如果不可用会返回 Nothing。
/// </summary>
public static string McDownloadForgeRecommendedGet(string mcInstance)
{
try
{
if (mcInstance is null)
return null;
var list = ModBase.ReadFile(Path.Combine(ModBase.pathTemp, "Cache", "ForgeRecommendedList.json"));
if (list is null || string.IsNullOrEmpty(list))
{
ModBase.Log("[Download] 没有 Forge 推荐版本缓存文件");
return null;
}
var json = (JsonObject)ModBase.GetJson(list);
if (json is null || !(mcInstance ?? "null").Contains(".") || !json.ContainsKey(mcInstance))
return null;
return (json[mcInstance] ?? "").ToString();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"获取 Forge 推荐版本失败(" + (mcInstance ?? "null") + "",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
return null;
}
}
#endregion
#region NeoForge
public static void NeoForgeDownloadListItemPreload(StackPanel stack, List<ModDownload.DlNeoForgeListEntry> entries,
MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
// 如果只有一个版本,则不特别列出
if (entries.Count == 1)
return;
// 获取最新稳定版和测试版
ModDownload.DlNeoForgeListEntry freshStableVersion = null;
ModDownload.DlNeoForgeListEntry freshBetaVersion = null;
if (entries.Any())
foreach (var Entry in entries.ToList())
if (Entry.IsBeta)
{
if (freshBetaVersion is null)
freshBetaVersion = Entry;
}
else
{
freshStableVersion = Entry;
break;
}
else
ModBase.Log("[System] 未找到可用的 NeoForge 版本", ModBase.LogLevel.Debug);
// 显示各个版本
if (freshStableVersion is not null)
{
var fresh = NeoForgeDownloadListItem(freshStableVersion, onClick, isSaveOnly);
fresh.Info = string.IsNullOrEmpty(fresh.Info)
? Lang.Text("Download.Version.Fresh.Stable")
: Lang.Text("Download.Version.Fresh.Latest") + " | " + fresh.Info;
stack.Children.Add(fresh);
}
if (freshBetaVersion is not null)
{
var fresh = NeoForgeDownloadListItem(freshBetaVersion, onClick, isSaveOnly);
fresh.Info = string.IsNullOrEmpty(fresh.Info)
? Lang.Text("Download.Version.Fresh.Development")
: Lang.Text("Download.Version.Fresh.Latest") + " | " + fresh.Info;
stack.Children.Add(fresh);
}
// 添加间隔
stack.Children.Add(new TextBlock
{
Text = Lang.Text("Download.Version.AllVersions", entries.Count), HorizontalAlignment = HorizontalAlignment.Left,
Margin = new Thickness(6d, 13d, 0d, 4d)
});
}
public static MyListItem NeoForgeDownloadListItem(ModDownload.DlNeoForgeListEntry info,
MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
// 建立控件
var newItem = new MyListItem
{
Title = info.VersionName,
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = info,
Info = info.IsBeta ? Lang.Text("Download.Version.Type.Preview") : Lang.Text("Download.Version.Type.Stable"),
Logo = ModBase.pathImage + "Blocks/NeoForge.png"
};
newItem.Click += onClick;
// 建立菜单
if (isSaveOnly)
newItem.ContentHandler = NeoForgeSaveContMenuBuild;
else
newItem.ContentHandler = NeoForgeContMenuBuild;
// 结束
return newItem;
}
private static void NeoForgeContMenuBuild(MyListItem sender, EventArgs e)
{
var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveAs") };
ToolTipService.SetPlacement(btnSave, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnSave, 30d);
ToolTipService.SetHorizontalOffset(btnSave, 2d);
btnSave.Click += (sender, e) => NeoForgeSave_Click(sender, (RoutedEventArgs)e);
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (sender, e) => NeoForgeLog_Click(sender, (RoutedEventArgs)e);
sender.Buttons = new[] { btnSave, btnInfo };
}
private static void NeoForgeSaveContMenuBuild(MyListItem sender, EventArgs e)
{
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (sender, e) => NeoForgeLog_Click(sender, (RoutedEventArgs)e);
sender.Buttons = new[] { btnInfo };
}
private static void NeoForgeLog_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlNeoForgeListEntry info;
if (((dynamic)sender).Tag is not null)
info = (ModDownload.DlNeoForgeListEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
info = (ModDownload.DlNeoForgeListEntry)((dynamic)sender).Parent.Tag;
else
info = (ModDownload.DlNeoForgeListEntry)((dynamic)sender).Parent.Parent.Tag;
ModBase.OpenWebsite(info.UrlBase + "-changelog.txt");
}
public static void NeoForgeSave_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlNeoForgeListEntry info;
if (((dynamic)sender).Tag is not null)
info = (ModDownload.DlNeoForgeListEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
info = (ModDownload.DlNeoForgeListEntry)((dynamic)sender).Parent.Tag;
else
info = (ModDownload.DlNeoForgeListEntry)((dynamic)sender).Parent.Parent.Tag;
McDownloadForgelikeSave(info);
}
#endregion
#region Cleanroom
public static void CleanroomDownloadListItemPreload(StackPanel stack,
List<ModDownload.DlCleanroomListEntry> entries, MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
// 获取最新稳定版和测试版
// Dim FreshStableVersion As DlCleanroomListEntry = Nothing
ModDownload.DlCleanroomListEntry freshBetaVersion = null;
if (entries.Any())
freshBetaVersion = entries[0];
else
ModBase.Log("[System] 未找到可用的 Cleanroom 版本", ModBase.LogLevel.Debug);
if (freshBetaVersion is not null)
{
var fresh = CleanroomDownloadListItem(freshBetaVersion, onClick, isSaveOnly);
fresh.Info = string.IsNullOrEmpty(fresh.Info) ? Lang.Text("Download.Version.Fresh.Development") : Lang.Text("Download.Version.Fresh.Latest") + " | " + fresh.Info;
stack.Children.Add(fresh);
}
// 添加间隔
stack.Children.Add(new TextBlock
{
Text = Lang.Text("Download.Version.AllVersions", entries.Count), HorizontalAlignment = HorizontalAlignment.Left,
Margin = new Thickness(6d, 13d, 0d, 4d)
});
}
public static MyListItem CleanroomDownloadListItem(ModDownload.DlCleanroomListEntry info,
MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
// 建立控件
var newItem = new MyListItem
{
Title = info.VersionName,
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = info,
Info = info.IsBeta ? Lang.Text("Download.Version.Type.Preview") : Lang.Text("Download.Version.Type.Stable"),
Logo = ModBase.pathImage + "Blocks/Cleanroom.png"
};
newItem.Click += onClick;
// 建立菜单
if (isSaveOnly)
newItem.ContentHandler = CleanroomSaveContMenuBuild;
else
newItem.ContentHandler = CleanroomContMenuBuild;
// 结束
return newItem;
}
private static void CleanroomContMenuBuild(MyListItem sender, EventArgs e)
{
var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveAs") };
ToolTipService.SetPlacement(btnSave, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnSave, 30d);
ToolTipService.SetHorizontalOffset(btnSave, 2d);
btnSave.Click += (sender, _e) => CleanroomSave_Click(sender, (RoutedEventArgs)e);
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (sender, e) => CleanroomLog_Click(sender, (RoutedEventArgs)e);
sender.Buttons = new[] { btnSave, btnInfo };
}
private static void CleanroomSaveContMenuBuild(MyListItem sender, EventArgs e)
{
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (a, b) => CleanroomLog_Click(a, (dynamic)b);
sender.Buttons = new[] { btnInfo };
}
private static void CleanroomLog_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlCleanroomListEntry info;
if (((dynamic)sender).Tag is not null)
info = (ModDownload.DlCleanroomListEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
info = (ModDownload.DlCleanroomListEntry)((dynamic)sender).Parent.Tag;
else
info = (ModDownload.DlCleanroomListEntry)((dynamic)sender).Parent.Parent.Tag;
ModBase.OpenWebsite(info.UrlBase + "-changelog.txt");
}
public static void CleanroomSave_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlCleanroomListEntry info;
if (((dynamic)sender).Tag is not null)
info = (ModDownload.DlCleanroomListEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
info = (ModDownload.DlCleanroomListEntry)((dynamic)sender).Parent.Tag;
else
info = (ModDownload.DlCleanroomListEntry)((dynamic)sender).Parent.Parent.Tag;
McDownloadForgelikeSave(info);
}
#endregion
#region Fabric
public static void McDownloadFabricLoaderSave(JsonObject downloadInfo)
{
try
{
var url = downloadInfo["url"].ToString();
var fileName = ModBase.GetFileNameFromPath(url);
var version = ModBase.GetFileNameFromPath(downloadInfo["version"].ToString());
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Version.SelectSaveLocation"), fileName, Lang.Text("Download.Version.Installer.Fabric.Filter"));
if (!target.Contains(@"\"))
return;
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.FabricInstallerDownload", version) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 构造步骤加载器
var loaders = new List<ModLoader.LoaderBase>();
// 下载
// BMCLAPI 不支持 Fabric Installer 下载
var address = new List<string>();
address.Add(url);
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadMainFile"),
new List<DownloadFile> { new(address.ToArray(), target, new ModBase.FileChecker(1024 * 64)) })
{ ProgressWeight = 15d });
// 启动
var loader =
new ModLoader.LoaderCombo<JsonObject>(
Lang.Text("Minecraft.Download.Stage.FabricInstallerDownload", version), loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(downloadInfo);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 Fabric 安装器下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
/// <summary>
/// 获取下载某个 Fabric 实例的加载器列表。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadFabricLoader(string fabricVersion, string minecraftName,
string mcFolder = null, bool fixLibrary = true)
{
// 参数初始化
mcFolder = mcFolder ?? ModFolder.mcFolderSelected;
var isCustomFolder = (mcFolder ?? "") != (ModFolder.mcFolderSelected ?? "");
var id = "fabric-loader-" + fabricVersion + "-" + minecraftName;
var versionFolder = Path.Combine(mcFolder, "versions", id);
var loaders = new List<ModLoader.LoaderBase>();
// 下载 Json
minecraftName = minecraftName.Replace("∞", "infinite"); // 放在 ID 后面避免影响实例文件夹名称
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.ObtainFabricMainFileUrl"), task =>
{
// 启动依赖实例的下载
if (fixLibrary)
McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading, minecraftName);
task.Progress = 0.5d;
var safeName = minecraftName.Replace("∞", "infinite");
var bmclapiUrl = $"https://bmclapi2.bangbang93.com/fabric-meta/v2/versions/loader/{safeName}/{fabricVersion}/profile/json";
var officialUrl = $"https://meta.fabricmc.net/v2/versions/loader/{safeName}/{fabricVersion}/profile/json";
string json = null;
foreach (var url in new[] { bmclapiUrl, officialUrl })
{
try
{
json = Requester.FetchString(url, new RequestParam { UseBrowserUserAgent = true, Timeout = 5000, Retries = 2 });
if (json is not null) break;
}
catch (Exception ex)
{
ModBase.Log(ex, $"[Download] 从 {url} 下载 Fabric meta 失败");
}
}
if (json is null)
throw new Exception(Lang.Text("Minecraft.Download.Error.FabricMetaDownloadFailed",
$"{bmclapiUrl} and {officialUrl}"));
Directory.CreateDirectory(versionFolder);
File.WriteAllText(Path.Combine(versionFolder, id + ".json"), json, Encoding.UTF8);
task.output = new List<DownloadFile>();
})
{
ProgressWeight = 0.5d
});
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLoaderMainFile", "Fabric"),
new List<DownloadFile>()) { ProgressWeight = 2.5d });
// 下载支持库
if (fixLibrary)
{
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeFabricLibraries"),
task => task.output =
ModLibrary.McLibNetFilesFromInstance(new McInstance(versionFolder)))
{ ProgressWeight = 1d, show = false });
loaders.Add(
new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLabyModClientJson"),
new List<DownloadFile>()) { ProgressWeight = 8d });
}
return loaders;
}
#endregion
#region LegacyFabric
public static void McDownloadLegacyFabricLoaderSave(JsonObject downloadInfo)
{
try
{
var url = downloadInfo["url"].ToString();
var fileName = ModBase.GetFileNameFromPath(url);
var version = ModBase.GetFileNameFromPath(downloadInfo["version"].ToString());
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Version.SelectSaveLocation"), fileName, Lang.Text("Download.Version.Installer.LegacyFabric.Filter"));
if (!target.Contains(@"\"))
return;
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.LegacyFabricInstallerDownload", version) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 构造步骤加载器
var loaders = new List<ModLoader.LoaderBase>();
// 下载
var address = new List<string>();
address.Add(url);
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadMainFile"),
new List<DownloadFile> { new(address.ToArray(), target, new ModBase.FileChecker(1024 * 64)) })
{ ProgressWeight = 15d });
// 启动
var loader =
new ModLoader.LoaderCombo<JsonObject>(
Lang.Text("Minecraft.Download.Stage.LegacyFabricInstallerDownload", version), loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(downloadInfo);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 Legacy Fabric 安装器下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
/// <summary>
/// 获取下载某个 LegacyFabric 实例的加载器列表。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadLegacyFabricLoader(string legacyFabricVersion,
string minecraftName, string mcFolder = null, bool fixLibrary = true)
{
// 参数初始化
mcFolder = mcFolder ?? ModFolder.mcFolderSelected;
var isCustomFolder = (mcFolder ?? "") != (ModFolder.mcFolderSelected ?? "");
var id = "legacy-fabric-loader-" + legacyFabricVersion + "-" + minecraftName;
var versionFolder = Path.Combine(mcFolder, "versions", id);
var loaders = new List<ModLoader.LoaderBase>();
// 下载 Json
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.ObtainLegacyFabricMainFileUrl"), task =>
{
// 启动依赖实例的下载
if (fixLibrary)
McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading, minecraftName);
task.Progress = 0.5d;
// 构造文件请求
task.output = new List<DownloadFile>
{
new(
new[]
{
"https://meta.legacyfabric.net/v2/versions/loader/" + minecraftName + "/" +
legacyFabricVersion + "/profile/json"
}, Path.Combine(versionFolder, id + ".json"), new ModBase.FileChecker(isJson: true))
};
})
{
ProgressWeight = 0.5d
});
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLoaderMainFile", "Legacy Fabric"),
new List<DownloadFile>())
{ ProgressWeight = 2.5d });
// 下载支持库
if (fixLibrary)
{
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeLegacyFabricLibraries"),
task => task.output =
ModLibrary.McLibNetFilesFromInstance(new McInstance(versionFolder)))
{ ProgressWeight = 1d, show = false });
loaders.Add(new LoaderDownload(
Lang.Text("Minecraft.Download.Stage.DownloadLoaderLibraries", "Legacy Fabric"),
new List<DownloadFile>())
{ ProgressWeight = 8d });
}
return loaders;
}
#endregion
#region Fabric
public static MyListItem FabricDownloadListItem(JsonObject entry, MyListItem.ClickEventHandler onClick)
{
// 建立控件
var newItem = new MyListItem
{
Title = entry["version"].ToString().Replace("+build", ""),
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = entry["stable"].ToObject<bool>() ? Lang.Text("Download.Version.Type.Stable") : Lang.Text("Download.Version.Type.Preview"),
Logo = ModBase.pathImage + "Blocks/Fabric.png"
};
newItem.Click += onClick;
newItem.ContentHandler = FabricContMenuBuild;
// 结束
return newItem;
}
private static void FabricContMenuBuild(object sender, EventArgs e)
{
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (a, b) => FabricLog_Click(a, (dynamic)b);
((dynamic)sender).Buttons = new[] { btnInfo };
}
private static void FabricLog_Click(object sender, RoutedEventArgs e)
{
ModBase.OpenWebsite("https://fabricmc.net/blog");
}
public static MyListItem FabricApiDownloadListItem(ModComp.CompFile entry, MyListItem.ClickEventHandler onClick)
{
// 建立控件
var newItem = new MyListItem
{
Title = entry.DisplayName.Split("]")[1].Replace("Fabric API ", "").Replace(" build ", ".").Trim(),
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = entry.StatusDescription + Lang.Text("Download.Version.ReleaseDate", Lang.Date(entry.ReleaseDate, "g")),
Logo = ModBase.pathImage + "Blocks/Fabric.png"
};
newItem.Click += onClick;
// 结束
return newItem;
}
public static MyListItem OptiFabricDownloadListItem(ModComp.CompFile entry, MyListItem.ClickEventHandler onClick)
{
// 建立控件
var newItem = new MyListItem
{
Title = entry.DisplayName.ToLower().Replace("optifabric-", "").Replace(".jar", "").Trim().TrimStart('v'),
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = entry.StatusDescription + Lang.Text("Download.Version.ReleaseDate", Lang.Date(entry.ReleaseDate, "g")),
Logo = ModBase.pathImage + "Blocks/OptiFabric.png"
};
newItem.Click += onClick;
// 结束
return newItem;
}
#endregion
#region LegacyFabric
public static MyListItem LegacyFabricDownloadListItem(JsonObject entry, MyListItem.ClickEventHandler onClick)
{
// 建立控件
var newItem = new MyListItem
{
Title = entry["version"].ToString(),
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = entry["stable"].ToObject<bool>() ? Lang.Text("Download.Version.Type.Stable") : Lang.Text("Download.Version.Type.Preview"),
Logo = ModBase.pathImage + "Blocks/Fabric.png"
};
newItem.Click += onClick;
// 结束
return newItem;
}
public static MyListItem LegacyFabricApiDownloadListItem(ModComp.CompFile entry,
MyListItem.ClickEventHandler onClick)
{
// 建立控件
var newItem = new MyListItem
{
Title = entry.DisplayName.Replace("Legacy Fabric API ", ""),
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = entry.StatusDescription + Lang.Text("Download.Version.ReleaseDate", Lang.Date(entry.ReleaseDate, "g")),
Logo = ModBase.pathImage + "Blocks/Fabric.png"
};
newItem.Click += onClick;
// 结束
return newItem;
}
#endregion
#region LabyMod
public static void McDownloadLabyModProductionLoaderSave()
{
try
{
var url = "https://releases.labymod.net/api/v1/installer/production/java";
var fileName = "LabyMod4ProductionInstaller.jar";
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Version.SelectSaveLocation"), fileName, Lang.Text("Download.Version.Installer.LabyMod.Filter"));
if (!target.Contains(@"\"))
return;
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.LabyModInstallerDownload") ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 构造步骤加载器
var loaders = new List<ModLoader.LoaderBase>();
// 下载
var address = new List<string>();
address.Add(url);
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadMainFile"),
new List<DownloadFile> { new(address.ToArray(), target, new ModBase.FileChecker(1024 * 64)) })
{ ProgressWeight = 15d });
// 启动
var loader =
new ModLoader.LoaderCombo<JsonObject>(Lang.Text("Minecraft.Download.Stage.LabyModInstallerDownload"),
loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start();
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 LabyMod 安装器下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
public static void McDownloadLabyModSnapshotLoaderSave()
{
try
{
var url = "https://releases.labymod.net/api/v1/installer/snapshot/java";
var fileName = "LabyMod4SnapshotInstaller.jar";
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Version.SelectSaveLocation"), fileName, Lang.Text("Download.Version.Installer.LabyMod.Filter"));
if (!target.Contains(@"\"))
return;
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.LabyModInstallerDownload") ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 构造步骤加载器
var loaders = new List<ModLoader.LoaderBase>();
// 下载
var address = new List<string>();
address.Add(url);
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadMainFile"),
new List<DownloadFile> { new(address.ToArray(), target, new ModBase.FileChecker(1024 * 64)) })
{ ProgressWeight = 15d });
// 启动
var loader =
new ModLoader.LoaderCombo<JsonObject>(Lang.Text("Minecraft.Download.Stage.LabyModInstallerDownload"),
loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start();
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 LabyMod 安装器下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
/// <summary>
/// 获取下载某个 LabyMod 实例的加载器列表。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadLabyModLoader(string labyModCommitRef, string labyModChannel,
string minecraftName, string mcFolder = null, bool fixLibrary = true)
{
// 参数初始化
mcFolder = mcFolder ?? ModFolder.mcFolderSelected;
var isCustomFolder = (mcFolder ?? "") != (ModFolder.mcFolderSelected ?? "");
var id = "labymod-" + labyModCommitRef + "-" + minecraftName;
var versionFolder = Path.Combine(mcFolder, "versions", id);
var loaders = new List<ModLoader.LoaderBase>();
// 下载 Json
minecraftName = minecraftName.Replace("∞", "infinite"); // 放在 ID 后面避免影响实例文件夹名称
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.ObtainLabyModClientUrl"), task =>
{
// 启动依赖实例的下载
if (fixLibrary)
McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading, minecraftName,
$"https://releases.r2.labymod.net/api/v1/download/manifest/labymod4/{labyModChannel}/{minecraftName}/{labyModCommitRef}.json");
task.Progress = 0.5d;
// 构造文件请求
task.output = new List<DownloadFile>
{
new(
new[]
{
$"https://releases.r2.labymod.net/api/v1/download/manifest/labymod4/{labyModChannel}/{minecraftName}/{labyModCommitRef}.json"
}, Path.Combine(versionFolder, id + ".json"), new ModBase.FileChecker(isJson: true))
};
task.Progress = 1d;
})
{
ProgressWeight = 2d
});
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLoaderLibraries", "Fabric"),
new List<DownloadFile>())
{ ProgressWeight = 10d });
// 下载支持库
if (fixLibrary)
{
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeLabyModLibraries"),
task => task.output =
ModLibrary.McLibNetFilesFromInstance(new McInstance(versionFolder)))
{ ProgressWeight = 1d, show = false });
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLoaderLibraries", "LabyMod"),
new List<DownloadFile>())
{ ProgressWeight = 8d });
}
return loaders;
}
/// <summary>
/// 获取下载某个 Minecraft 实例的加载器列表。
/// 它必须安装到 PathMcFolder,但是可以自定义实例名(不过自定义的实例名不会修改 Json 中的 id 项)。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadLabyModClientLoader(string id, string labyChannel,
string labyCommitRef, string versionName = null)
{
versionName = versionName ?? id;
var versionFolder = Path.Combine(ModFolder.mcFolderSelected, "versions", versionName) + @"\";
var loaders = new List<ModLoader.LoaderBase>();
// 下载支持库文件
var loadersLib = new List<ModLoader.LoaderBase>();
loadersLib.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeVanillaAndLabyModLibrariesSide"), task =>
{
ModBase.WaitForFileReady(Path.Combine(versionFolder, versionName + ".json"));
ModBase.Log("[Download] 开始分析原版与 LabyMod 支持库文件:" + versionFolder);
task.output = ModLibrary.McLibNetFilesFromInstance(new McInstance(versionFolder));
})
{
ProgressWeight = 1d,
show = false
});
loadersLib.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadVanillaAndLabyModLibrariesSide"),
new List<DownloadFile>())
{ ProgressWeight = 13d, show = false });
loaders.Add(new ModLoader.LoaderCombo<string>(mcDownloadClientLibName, loadersLib)
{ block = false, ProgressWeight = 14d });
// 下载资源文件
var loadersAssets = new List<ModLoader.LoaderBase>();
loadersAssets.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeAssetsIndex.Side"), task =>
{
try
{
var version = new McInstance(versionFolder);
task.output = new List<DownloadFile> { ModDownload.DlClientAssetIndexGet(version) };
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.AssetIndexAnalysisFailed"), ex);
}
// 顺手添加 Json 项目
try
{
var versionJson = (JsonObject)ModBase.GetJson(ModBase.ReadFile(Path.Combine(versionFolder, versionName + ".json")));
versionJson.Add("clientVersion", id);
ModBase.WriteFile(Path.Combine(versionFolder, versionName + ".json"), versionJson.ToString());
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.AddClientVersionFailed"), ex);
}
})
{
ProgressWeight = 1d,
show = false
});
loadersAssets.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadAssetsIndex.Side"),
new List<DownloadFile>())
{ ProgressWeight = 3d, show = false });
loadersAssets.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeRequiredAssets.Side"), task =>
{
ModLoader.LoaderBase argprogressFeed = task;
task.output =
ModAssets.McAssetsFixList(new McInstance(versionFolder), true, ref argprogressFeed);
task = (ModLoader.LoaderTask<string, List<DownloadFile>>)argprogressFeed;
})
{
ProgressWeight = 3d,
show = false
});
loadersAssets.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadAssets.Side"),
new List<DownloadFile>())
{ ProgressWeight = 14d, show = false });
loaders.Add(
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.DownloadVanillaAssets"),
loadersAssets) { block = false, ProgressWeight = 21d });
return loaders;
}
#endregion
#region LabyMod
public static MyListItem LabyModDownloadListItem(JsonObject entry, MyListItem.ClickEventHandler onClick)
{
// 建立控件
var newItem = new MyListItem
{
Title = entry["version"] + " " + (entry["channel"].ToString().Contains("snapshot") ? Lang.Text("Download.Version.Type.Snapshot") : Lang.Text("Download.Version.Type.Stable")),
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = entry["channel"].ToString().Contains("snapshot") ? Lang.Text("Download.Version.Type.Snapshot") : Lang.Text("Download.Version.Type.Stable"),
Logo = ModBase.pathImage + "Blocks/LabyMod.png"
};
newItem.Click += onClick;
newItem.ContentHandler = LabyModContMenuBuild;
// 结束
return newItem;
}
private static void LabyModContMenuBuild(object sender, EventArgs e)
{
var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveAs") };
ToolTipService.SetPlacement(btnSave, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnSave, 30d);
ToolTipService.SetHorizontalOffset(btnSave, 2d);
btnSave.Click += (a, b) => LabyModSave_Click(a, (dynamic)b);
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (a, b) => LabyModLog_Click(a, (dynamic)b);
((dynamic)sender).Buttons = new[] { btnSave, btnInfo };
}
private static void LabyModLog_Click(object sender, RoutedEventArgs e)
{
ModBase.OpenWebsite("https://www.labymod.net/zh_Hans/download");
}
private static void LabyModSave_Click(object sender, RoutedEventArgs e)
{
JsonObject version;
if (((dynamic)sender).Tag is not null)
version = (JsonObject)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
version = (JsonObject)((dynamic)sender).Parent.Tag;
else
version = (JsonObject)((dynamic)sender).Parent.Parent.Tag;
if ((string)version["channel"] == "snapshot")
McDownloadLabyModSnapshotLoaderSave();
else
McDownloadLabyModProductionLoaderSave();
}
#endregion
#region
/// <summary>
/// 安装请求。
/// </summary>
public class McInstallRequest
{
/// <summary>
/// 欲下载的 Cleanroom。
/// </summary>
public ModDownload.DlCleanroomListEntry cleanroomEntry = null;
// 若要下载 Cleanroom,则需要在下面两项中完成至少一项
/// <summary>
/// 欲下载的 Cleanroom 版本名。
/// </summary>
public string cleanroomVersion;
/// <summary>
/// 欲下载的 Fabric API 信息。
/// </summary>
public ModComp.CompFile fabricApi = null;
/// <summary>
/// 欲下载的 Fabric Loader 版本名。
/// </summary>
public string fabricVersion = null;
/// <summary>
/// 欲下载的 Forge。
/// </summary>
public ModDownload.DlForgeVersionEntry forgeEntry = null;
// 若要下载 Forge,则需要在下面两项中完成至少一项
/// <summary>
/// 欲下载的 Forge 版本名。接受例如 36.1.4 / 14.23.5.2859 / 1.19-41.1.0 的输入。
/// </summary>
public string forgeVersion;
/// <summary>
/// 欲下载的 LabyMod 通道。
/// </summary>
public string labyModChannel = null;
/// <summary>
/// 欲下载的 LabyMod 版本。
/// </summary>
public string labyModCommitRef = null;
/// <summary>
/// 欲下载的 Legacy Fabric API 信息。
/// </summary>
public ModComp.CompFile legacyFabricApi = null;
/// <summary>
/// 欲下载的 Legacy Fabric Loader 版本名。
/// </summary>
public string legacyFabricVersion = null;
/// <summary>
/// 欲下载的 LiteLoader 详细信息。
/// </summary>
public ModDownload.DlLiteLoaderListEntry liteLoaderEntry = null;
/// <summary>
/// 可选。欲下载的 Minecraft Json 地址。
/// </summary>
public string minecraftJson = null;
/// <summary>
/// 必填。欲下载的 Minecraft 的版本名。
/// </summary>
public string minecraftName = null;
/// <summary>
/// 若 MMC 整合包安装包含特殊参数,则填写此项。
/// </summary>
public ModModpack.MMCPackInfo mmcPackInfo = null;
/// <summary>
/// 欲下载的 NeoForge。
/// </summary>
public ModDownload.DlNeoForgeListEntry neoForgeEntry = null;
// 若要下载 NeoForge,则需要在下面两项中完成至少一项
/// <summary>
/// 欲下载的 NeoForge 版本名。
/// </summary>
public string neoForgeVersion;
/// <summary>
/// 欲下载的 OptiFabric 信息。
/// </summary>
public ModComp.CompFile optiFabric = null;
/// <summary>
/// 欲下载的 OptiFine 详细信息。
/// </summary>
public ModDownload.DlOptiFineListEntry optiFineEntry;
// 若要下载 OptiFine,则需要在下面两项中完成至少一项
/// <summary>
/// 欲下载的 OptiFine 版本名。例如 HD_U_F6_pre1。
/// </summary>
public string optiFineVersion;
/// <summary>
/// 必填。安装目标文件夹。
/// </summary>
public string targetInstanceFolder;
/// <summary>
/// 必填。安装目标实例名称。
/// </summary>
public string targetInstanceName;
}
/// <summary>
/// 在加载器状态改变后显示一条提示。
/// 不会进行任何其他操作。
/// </summary>
public static void LoaderStateChangedHintOnly(object loaderObj)
{
var loader = (ModLoader.LoaderBase)loaderObj;
switch (loader.State)
{
case ModBase.LoadState.Finished:
HintService.Hint($"{loader.name}{Lang.Text("Common.Status.Success")}", HintType.Success);
break;
case ModBase.LoadState.Failed:
HintService.Hint($"{loader.name}{Lang.Text("Common.Status.Failure")}{loader.Error.Message}", HintType.Error);
break;
case ModBase.LoadState.Aborted:
HintService.Hint($"{loader.name}{Lang.Text("Common.Status.Cancelled")}");
break;
}
}
/// <summary>
/// 安装加载器状态改变后进行提示和重载文件夹列表的方法。
/// </summary>
public static void McInstallState(object loaderObj)
{
var loader = (ModLoader.LoaderBase)loaderObj;
var combo = (ModLoader.LoaderCombo)loader;
switch (loader.State)
{
case ModBase.LoadState.Finished:
{
if (Config.Download.AutoSelectInstance)
{
var versionName = loader.name;
ModBase.WriteIni(ModFolder.mcFolderSelected + "PCL.ini", "Version",
versionName.Remove(versionName.Length - 3, 3));
}
ModBase.WriteIni(ModFolder.mcFolderSelected + "PCL.ini", "InstanceCache",
""); // 清空缓存(合并安装会先生成文件夹,这会在刷新时误判为可以使用缓存)
ModBase.DeleteDirectory($"{combo.input}PCLInstallBackups\\");
HintService.Hint($"{loader.name}{Lang.Text("Common.Status.Success")}",
HintType.Success);
break;
}
case ModBase.LoadState.Failed:
{
HintService.Hint(
$"{loader.name}{Lang.Text("Common.Status.Failure")}{loader.Error.Message}",
HintType.Error);
break;
}
case ModBase.LoadState.Aborted:
{
HintService.Hint($"{loader.name}{Lang.Text("Common.Status.Cancelled")}");
break;
}
case ModBase.LoadState.Loading:
{
return; // 不重新加载实例列表
}
}
if (loader.State != ModBase.LoadState.Finished &&
Directory.Exists(
$"{combo.input}PCLInstallBackups\\")) // 实例修改失败回滚
{
ModBase.CopyDirectory(
$"{combo.input}PCLInstallBackups\\",
(string)combo.input);
File.Delete($"{combo.input}.pclignore");
ModBase.DeleteDirectory(
$"{combo.input}PCLInstallBackups\\");
}
else
{
McInstallFailedClearFolder(loader);
}
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
ModLoader.LoaderFolderRunType.ForceRun, 1, @"versions\");
}
public static void McInstallFailedClearFolder(object loader)
{
try
{
Thread.Sleep(1000); // 防止存在尚未完全释放的文件,导致清理失败(例如整合包安装)
if (((ModLoader.LoaderBase)loader).State == ModBase.LoadState.Failed ||
((ModLoader.LoaderBase)loader).State == ModBase.LoadState.Aborted)
{
// 删除实例文件夹
ModBase.Log($"[Download] 由于下载失败或取消,清理实例文件夹:{((ModLoader.LoaderCombo)loader).input}", ModBase.LogLevel.Developer);
var instancePath = (string)((ModLoader.LoaderCombo)loader).input;
((DynamicCacheConfigStorage)ConfigService.GetProvider(ConfigSource.GameInstance)).InvalidateCache(instancePath);
ModBase.DeleteDirectory(instancePath);
}
}
catch (Exception ex)
{
ModBase.Log(ex, "下载失败或取消后清理实例文件夹失败");
}
}
private const string mcInstallDefaultType = "安装";
/// <summary>
/// 进行合并安装。返回是否已经开始安装(例如如果没有安装 Java 则会进行提示并返回 False)
/// </summary>
public static bool McInstall(McInstallRequest request, string type = mcInstallDefaultType)
{
try
{
var subLoaders = McInstallLoader(request, ignoreDump: type != mcInstallDefaultType);
if (subLoaders is null)
return false;
var loader = new ModLoader.LoaderCombo<string>(request.targetInstanceName + " " + type, subLoaders)
{ OnStateChanged = McInstallState };
// 启动
loader.Start(request.targetInstanceFolder);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
return true;
}
catch (ModBase.CancelledException ex)
{
return false;
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始合并安装失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
try
{
if (Directory.Exists(request.targetInstanceFolder))
{
var files = Directory.GetFiles(request.targetInstanceFolder);
var dirs = Directory.GetDirectories(request.targetInstanceFolder);
if (files.Length <= 1 && dirs.Length == 0)
{
((DynamicCacheConfigStorage)ConfigService.GetProvider(ConfigSource.GameInstance))
.InvalidateCache(request.targetInstanceFolder);
ModBase.DeleteDirectory(request.targetInstanceFolder);
}
}
}
catch (Exception innerEx)
{
ModBase.Log(innerEx, "清理未完成的实例文件夹失败");
}
return false;
}
}
/// <summary>
/// 获取合并安装加载器列表,并进行前期的缓存清理与 Java 检查工作。
/// </summary>
/// <exception cref="ModBase.CancelledException" />
public static List<ModLoader.LoaderBase> McInstallLoader(McInstallRequest request, bool dontFixLibraries = false,
bool ignoreDump = false)
{
// 获取缓存目录(安装 Mod 加载器的文件夹不能包含空格)
var tempMcFolder = ModMain.RequestTaskTempFolder(request.optiFineEntry is not null ||
request.forgeEntry is not null ||
request.neoForgeEntry is not null);
// 获取参数
var instanceFolder = Path.Combine(ModFolder.mcFolderSelected, "versions", request.targetInstanceName);
if (Directory.Exists(tempMcFolder))
ModBase.DeleteDirectory(tempMcFolder);
string optiFineFolder = null;
if (request.optiFineVersion is not null)
{
if (request.optiFineVersion.Contains("_HD_U_"))
request.optiFineVersion = "HD_U_" + request.optiFineVersion.AfterLast("_HD_U_"); // #735
request.optiFineEntry = new ModDownload.DlOptiFineListEntry
{
DisplayName = request.minecraftName + " " + request.optiFineVersion.Replace("HD_U_", "")
.Replace("_", "").Replace("pre", " pre"),
Inherit = request.minecraftName,
IsPreview = request.optiFineVersion.ContainsF("pre", true),
NameVersion = request.minecraftName + "-OptiFine_" + request.optiFineVersion,
NameFile = (request.optiFineVersion.ContainsF("pre", true) ? "preview_" : "") + "OptiFine_" +
request.minecraftName + "_" + request.optiFineVersion + ".jar"
};
}
if (request.optiFineEntry is not null)
optiFineFolder = Path.Combine(tempMcFolder, "versions", request.optiFineEntry.NameVersion);
string forgeFolder = null;
if (request.forgeEntry is not null)
request.forgeVersion = request.forgeVersion ?? request.forgeEntry.VersionName;
if (request.forgeVersion is not null)
forgeFolder = Path.Combine(tempMcFolder, "versions", "forge-" + request.forgeVersion);
string neoForgeFolder = null;
if (request.neoForgeEntry is not null)
request.neoForgeVersion = request.neoForgeVersion ?? request.neoForgeEntry.VersionName;
if (request.neoForgeVersion is not null)
neoForgeFolder = Path.Combine(tempMcFolder, "versions", "neoforge-" + request.neoForgeVersion);
string cleanroomFolder = null;
if (request.cleanroomEntry is not null)
request.cleanroomVersion = request.cleanroomVersion ?? request.cleanroomEntry.VersionName;
if (request.cleanroomVersion is not null)
cleanroomFolder = Path.Combine(tempMcFolder, "versions", "cleanroom-" + request.cleanroomVersion);
string fabricFolder = null;
if (request.fabricVersion is not null)
fabricFolder = Path.Combine(tempMcFolder, "versions", "fabric-loader-" + request.fabricVersion + "-" +
request.minecraftName);
string legacyFabricFolder = null;
if (request.legacyFabricVersion is not null)
legacyFabricFolder = Path.Combine(tempMcFolder, "versions", "legacy-fabric-loader-" + request.legacyFabricVersion + "-" +
request.minecraftName);
string labyModFolder = null;
if (request.labyModCommitRef is not null)
labyModFolder = Path.Combine(tempMcFolder, "versions", "labymod-" + request.labyModCommitRef + "-" +
request.minecraftName);
string liteLoaderFolder = null;
if (request.liteLoaderEntry is not null)
liteLoaderFolder = Path.Combine(tempMcFolder, "versions", request.minecraftName + "-LiteLoader");
// 判断 OptiFine 是否作为 Mod 进行下载
var modable = request.fabricVersion is not null || request.legacyFabricVersion is not null ||
request.forgeEntry is not null || request.neoForgeEntry is not null ||
request.liteLoaderEntry is not null;
var modsTempFolder = Path.Combine(tempMcFolder, "mods") + @"\";
var optiFineAsMod = request.optiFineEntry is not null && modable; // 选择了 OptiFine 与任意 Mod 加载器
if (optiFineAsMod)
{
ModBase.Log("[Download] OptiFine 将作为 Mod 进行下载");
if (request.liteLoaderEntry is not null)
optiFineFolder = CombineCacheSubfolder(modsTempFolder, request.minecraftName);
else
optiFineFolder = modsTempFolder;
}
// 记录日志
if (optiFineFolder is not null)
ModBase.Log("[Download] OptiFine 缓存:" + optiFineFolder);
if (forgeFolder is not null)
ModBase.Log("[Download] Forge 缓存:" + forgeFolder);
if (neoForgeFolder is not null)
ModBase.Log("[Download] NeoForge 缓存:" + neoForgeFolder);
if (cleanroomFolder is not null)
ModBase.Log("[Download] Cleanroom 缓存:" + cleanroomFolder);
if (fabricFolder is not null)
ModBase.Log("[Download] Fabric 缓存:" + fabricFolder);
if (legacyFabricFolder is not null)
ModBase.Log("[Download] LegacyFabric 缓存:" + legacyFabricFolder);
if (labyModFolder is not null)
ModBase.Log("[Download] LabyMod 缓存:" + labyModFolder);
if (liteLoaderFolder is not null)
ModBase.Log("[Download] LiteLoader 缓存:" + liteLoaderFolder);
ModBase.Log("[Download] 对应的原版版本:" + request.minecraftName);
// 重复实例检查
if (File.Exists(Path.Combine(instanceFolder, request.targetInstanceName + ".json")) && !ignoreDump)
{
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceAlreadyExists", request.targetInstanceName, ""),
HintType.Error);
throw new ModBase.CancelledException();
}
var loaderList = new List<ModLoader.LoaderBase>();
// 添加忽略标识
loaderList.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Minecraft.Download.Stage.AddIgnoreFlag"),
_ => ModBase.WriteFile(Path.Combine(instanceFolder, ".pclignore"), "用于临时地在 PCL 的实例列表中屏蔽此实例。"))
{ show = false, block = false });
// Fabric API
if (request.fabricApi is not null)
loaderList.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadFabricApi"),
new List<DownloadFile>
{
request.fabricApi.ToNetFile(modsTempFolder, ModComp.DownloadReason.Dependency, null,
ModComp.CompLoaderType.Fabric)
})
{ ProgressWeight = 3d, block = false });
// LegacyFabric API
if (request.legacyFabricApi is not null)
loaderList.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLegacyFabricApi"),
new List<DownloadFile>
{
request.legacyFabricApi.ToNetFile(modsTempFolder, ModComp.DownloadReason.Dependency, null,
ModComp.CompLoaderType.Fabric)
})
{ ProgressWeight = 3d, block = false });
// OptiFabric
if (request.optiFabric is not null)
loaderList.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadOptiFabric"),
new List<DownloadFile>
{
request.optiFabric.ToNetFile(modsTempFolder, ModComp.DownloadReason.Dependency, null,
ModComp.CompLoaderType.Fabric)
})
{ ProgressWeight = 3d, block = false });
// LabyMod
if (request.labyModCommitRef is not null)
{
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "LabyMod", request.labyModCommitRef),
McDownloadLabyModLoader(request.labyModCommitRef, request.labyModChannel, request.minecraftName,
tempMcFolder, false)) { show = false, ProgressWeight = 10d, block = true });
goto LabyModSkip;
}
// 原版
var clientLoader = new ModLoader.LoaderCombo<string>(
Lang.Text(
"Minecraft.Download.Stage.LoaderDownloadCombo",
Lang.Text("Minecraft.Version.Vanilla"),
request.minecraftName
),
McDownloadClientLoader(
request.minecraftName, request.minecraftJson, request.targetInstanceName
)
)
{
show = false,
ProgressWeight = 39d,
block = request.forgeVersion is null && request.neoForgeVersion is null && request.optiFineEntry is null &&
request.fabricVersion is null && request.liteLoaderEntry is null &&
request.cleanroomEntry is null && request.legacyFabricVersion is null
};
loaderList.Add(clientLoader);
// OptiFine
if (request.optiFineEntry is not null)
{
if (optiFineAsMod)
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "OptiFine",
request.optiFineEntry.DisplayName),
McDownloadOptiFineSaveLoader(request.optiFineEntry,
Path.Combine(optiFineFolder, request.optiFineEntry.NameFile)))
{
show = false,
ProgressWeight = 16d,
block = request.forgeVersion is null && request.neoForgeVersion is null &&
request.fabricVersion is null && request.liteLoaderEntry is null
});
else
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "OptiFine",
request.optiFineEntry.DisplayName),
McDownloadOptiFineLoader(request.optiFineEntry, tempMcFolder, clientLoader,
request.targetInstanceFolder, false))
{
show = false,
ProgressWeight = 24d,
block = request.forgeVersion is null && request.neoForgeVersion is null &&
request.fabricVersion is null && request.liteLoaderEntry is null
});
}
// Forge
if (request.forgeVersion is not null)
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "Forge", request.forgeVersion),
McDownloadForgelikeLoader(ModDownload.DlForgelikeEntry.ForgelikeType.Forge, request.forgeVersion, "forge-" + request.forgeVersion,
request.minecraftName, request.forgeEntry, tempMcFolder, clientLoader,
request.targetInstanceFolder))
{
show = false, ProgressWeight = 25d,
block = request.fabricVersion is null && request.liteLoaderEntry is null &&
request.neoForgeEntry is null
});
// NeoForge
if (request.neoForgeVersion is not null)
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "NeoForge", request.neoForgeVersion),
McDownloadForgelikeLoader(ModDownload.DlForgelikeEntry.ForgelikeType.NeoForge, request.neoForgeVersion, "neoforge-" + request.neoForgeVersion,
request.minecraftName, request.neoForgeEntry, tempMcFolder, clientLoader,
request.targetInstanceFolder))
{
show = false, ProgressWeight = 25d,
block = request.forgeEntry is null && request.fabricVersion is null && request.liteLoaderEntry is null
});
// Cleanroom
if (request.cleanroomVersion is not null)
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "Cleanroom", request.cleanroomVersion),
McDownloadForgelikeLoader(ModDownload.DlForgelikeEntry.ForgelikeType.Cleanroom, request.cleanroomVersion,
"cleanroom-" + request.cleanroomVersion, request.minecraftName, request.cleanroomEntry,
tempMcFolder, clientLoader, request.targetInstanceFolder))
{
show = false, ProgressWeight = 25d,
block = request.forgeEntry is null && request.fabricVersion is null && request.liteLoaderEntry is null
});
// LiteLoader
if (request.liteLoaderEntry is not null)
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "LiteLoader", request.minecraftName),
McDownloadLiteLoaderLoader(request.liteLoaderEntry, tempMcFolder, clientLoader, false))
{
show = false,
ProgressWeight = 1d,
block = request.fabricVersion is null
});
// Fabric
if (request.fabricVersion is not null)
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "Fabric", request.fabricVersion),
McDownloadFabricLoader(request.fabricVersion, request.minecraftName, tempMcFolder, false))
{
show = false,
ProgressWeight = 2d,
block = true
});
// LegacyFabric
if (request.legacyFabricVersion is not null)
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "Legacy Fabric", request.legacyFabricVersion),
McDownloadLegacyFabricLoader(request.legacyFabricVersion, request.minecraftName, tempMcFolder, false))
{
show = false,
ProgressWeight = 2d,
block = true
});
LabyModSkip: ;
// 合并安装
loaderList.Add(new ModLoader.LoaderTask<string, string>(Lang.Text("Minecraft.Download.Stage.InstallGame"),
task =>
{
// 合并 JSON
MergeJson(instanceFolder, instanceFolder, optiFineFolder, optiFineAsMod, forgeFolder, request.forgeVersion,
neoForgeFolder, request.neoForgeVersion, cleanroomFolder, request.cleanroomVersion, fabricFolder,
labyModFolder, request.labyModChannel, liteLoaderFolder, request.mmcPackInfo,
legacyFabricFolder);
task.Progress = 0.2d;
// 迁移文件
if (Directory.Exists(Path.Combine(tempMcFolder, "libraries")))
ModBase.CopyDirectory(Path.Combine(tempMcFolder, "libraries"), Path.Combine(ModFolder.mcFolderSelected, "libraries"));
task.Progress = 0.8d;
// 创建 Mod 和资源包文件夹
var modsFolder = Path.Combine(new McInstance(instanceFolder).PathIndie, "mods"); // 版本隔离信息在此时被决定
if (Directory.Exists(modsTempFolder))
{
ModBase.CopyDirectory(modsTempFolder, modsFolder);
}
else if (modable)
{
Directory.CreateDirectory(modsFolder);
ModBase.Log("[Download] 自动创建 Mod 文件夹:" + modsFolder);
}
var resourcepacksFolder = Path.Combine(new McInstance(instanceFolder).PathIndie, "resourcepacks");
Directory.CreateDirectory(resourcepacksFolder);
ModBase.Log("[Download] 自动创建资源包文件夹:" + resourcepacksFolder);
})
{
ProgressWeight = 2d,
block = true
});
// 补全文件
if (!dontFixLibraries && (request.optiFineEntry is not null ||
(request.forgeVersion is not null &&
Convert.ToDouble(request.forgeVersion.BeforeFirst(".")) >= 20d) ||
request.neoForgeVersion is not null || request.fabricVersion is not null ||
request.cleanroomVersion is not null ||
request.liteLoaderEntry is not null || request.labyModCommitRef is not null))
{
var loadersLib = new List<ModLoader.LoaderBase>();
if (request.labyModCommitRef is not null)
{
var labyModClientLoader = new ModLoader.LoaderCombo<string>(
Lang.Text(
"Minecraft.Download.Stage.LoaderDownloadCombo",
Lang.Text("Minecraft.Version.Vanilla"), request.minecraftName
),
McDownloadLabyModClientLoader(
request.minecraftName, request.labyModChannel,
request.labyModCommitRef, request.targetInstanceName
)
)
{
show = false, ProgressWeight = 39d, block = false
};
loaderList.Add(labyModClientLoader);
}
else
{
loadersLib.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeGameLibrariesSide"),
task => task.output =
ModLibrary.McLibNetFilesFromInstance(new McInstance(instanceFolder)))
{ ProgressWeight = 1d, show = false });
loadersLib.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadGameLibrariesSide"),
new List<DownloadFile>())
{ ProgressWeight = 7d, show = false });
loaderList.Add(
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.DownloadGameLibraries"),
loadersLib) { ProgressWeight = 8d });
}
}
// 删除忽略标识
loaderList.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Minecraft.Download.Stage.DeleteIgnoreFlag"),
_ => File.Delete(Path.Combine(instanceFolder, ".pclignore")))
{ show = false });
// 总加载器
return loaderList;
}
/// <summary>
/// 将多个实例 JSON 进行合并,如果目标已存在则直接覆盖。失败会抛出异常。
/// </summary>
private static void MergeJson(string outputFolder, string minecraftFolder, string optiFineFolder = null,
bool optiFineAsMod = false, string forgeFolder = null, string forgeVersion = null, string neoForgeFolder = null,
string neoForgeVersion = null, string cleanroomFolder = null, string cleanroomVersion = null,
string fabricFolder = null, string labyModFolder = null,
string labyModChannel = null, string liteLoaderFolder = null, ModModpack.MMCPackInfo mMCPackInfo = null,
string legacyFabricFolder = null)
{
ModBase.Log("[Download] 开始进行实例合并,输出:" + outputFolder + "Minecraft" + minecraftFolder +
(optiFineFolder is not null ? "OptiFine" + optiFineFolder : "") +
(forgeFolder is not null ? "Forge" + forgeFolder : "") +
(neoForgeFolder is not null ? "NeoForge" + neoForgeFolder : "") +
(cleanroomFolder is not null ? "Cleanroom" + cleanroomFolder : "") +
(liteLoaderFolder is not null ? "LiteLoader" + liteLoaderFolder : "") +
(fabricFolder is not null ? "Fabric" + fabricFolder : "") +
(legacyFabricFolder is not null ? "LegacyFabric" + legacyFabricFolder : "") +
(labyModFolder is not null ? "LabyMod" + labyModFolder : ""));
Directory.CreateDirectory(outputFolder);
var hasOptiFine = optiFineFolder is not null && !optiFineAsMod;
var hasForge = forgeFolder is not null;
var hasLegacyFabric = legacyFabricFolder is not null;
var hasNeoForge = neoForgeFolder is not null;
var hasCleanroom = cleanroomFolder is not null;
var hasLiteLoader = liteLoaderFolder is not null;
var hasFabric = fabricFolder is not null;
var hasLabyMod = labyModFolder is not null;
string outputName;
string minecraftName;
string optiFineName;
string forgeName;
string neoForgeName;
string cleanroomName;
string liteLoaderName;
string fabricName;
string legacyFabricName;
string labyModName;
string outputJsonPath;
string minecraftJsonPath;
string optiFineJsonPath = null;
string forgeJsonPath = null;
string neoForgeJsonPath = null;
string cleanroomJsonPath = null;
string liteLoaderJsonPath = null;
string fabricJsonPath = null;
string labyModJsonPath = null;
string legacyFabricJsonPath = null;
string outputJar;
string minecraftJar;
#region
if (!outputFolder.EndsWithF(@"\"))
outputFolder += @"\";
outputName = ModBase.GetFolderNameFromPath(outputFolder);
outputJsonPath = Path.Combine(outputFolder, outputName + ".json");
outputJar = Path.Combine(outputFolder, outputName + ".jar");
if (!minecraftFolder.EndsWithF(@"\"))
minecraftFolder += @"\";
minecraftName = ModBase.GetFolderNameFromPath(minecraftFolder);
minecraftJsonPath = Path.Combine(minecraftFolder, minecraftName + ".json");
minecraftJar = Path.Combine(minecraftFolder, minecraftName + ".jar");
if (hasOptiFine)
{
if (!optiFineFolder.EndsWithF(@"\"))
optiFineFolder += @"\";
optiFineName = ModBase.GetFolderNameFromPath(optiFineFolder);
optiFineJsonPath = Path.Combine(optiFineFolder, optiFineName + ".json");
}
if (hasForge)
{
if (!forgeFolder.EndsWithF(@"\"))
forgeFolder += @"\";
forgeName = ModBase.GetFolderNameFromPath(forgeFolder);
forgeJsonPath = Path.Combine(forgeFolder, forgeName + ".json");
}
if (hasNeoForge)
{
if (!neoForgeFolder.EndsWithF(@"\"))
neoForgeFolder += @"\";
neoForgeName = ModBase.GetFolderNameFromPath(neoForgeFolder);
neoForgeJsonPath = Path.Combine(neoForgeFolder, neoForgeName + ".json");
}
if (hasCleanroom)
{
if (!cleanroomFolder.EndsWithF(@"\"))
cleanroomFolder += @"\";
cleanroomName = ModBase.GetFolderNameFromPath(cleanroomFolder);
cleanroomJsonPath = Path.Combine(cleanroomFolder, cleanroomName + ".json");
}
if (hasLiteLoader)
{
if (!liteLoaderFolder.EndsWithF(@"\"))
liteLoaderFolder += @"\";
liteLoaderName = ModBase.GetFolderNameFromPath(liteLoaderFolder);
liteLoaderJsonPath = Path.Combine(liteLoaderFolder, liteLoaderName + ".json");
}
if (hasFabric)
{
if (!fabricFolder.EndsWithF(@"\"))
fabricFolder += @"\";
fabricName = ModBase.GetFolderNameFromPath(fabricFolder);
fabricJsonPath = Path.Combine(fabricFolder, fabricName + ".json");
}
if (hasLegacyFabric)
{
if (!legacyFabricFolder.EndsWithF(@"\"))
legacyFabricFolder += @"\";
legacyFabricName = ModBase.GetFolderNameFromPath(legacyFabricFolder);
legacyFabricJsonPath = Path.Combine(legacyFabricFolder, legacyFabricName + ".json");
}
if (hasLabyMod)
{
if (!labyModFolder.EndsWithF(@"\"))
labyModFolder += @"\";
labyModName = ModBase.GetFolderNameFromPath(labyModFolder);
labyModJsonPath = Path.Combine(labyModFolder, labyModName + ".json");
}
#endregion
JsonObject outputJson;
JsonObject minecraftJson = null;
JsonObject optiFineJson = null;
JsonObject forgeJson = null;
JsonObject neoForgeJson = null;
JsonObject legacyFabricJson = null;
JsonObject cleanroomJson = null;
JsonObject liteLoaderJson = null;
JsonObject fabricJson = null;
JsonObject labyModJson = null;
#region
var minecraftJsonText = ModBase.ReadFile(minecraftJsonPath);
if (!hasLabyMod)
{
if (!minecraftJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "Minecraft", minecraftJsonPath,
minecraftJsonText.Substring(0, Math.Min(minecraftJsonText.Length, 1000))));
minecraftJson = (JsonObject)ModBase.GetJson(minecraftJsonText);
}
if (hasOptiFine)
{
var optiFineJsonText = ModBase.ReadFile(optiFineJsonPath);
if (!optiFineJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "OptiFine", optiFineJsonPath,
optiFineJsonText.Substring(0, Math.Min(optiFineJsonText.Length, 1000))));
optiFineJson = (JsonObject)ModBase.GetJson(optiFineJsonText);
}
if (hasForge)
{
var forgeJsonText = ModBase.ReadFile(forgeJsonPath);
if (!forgeJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "Forge", forgeJsonPath,
forgeJsonText.Substring(0, Math.Min(forgeJsonText.Length, 1000))));
forgeJson = (JsonObject)ModBase.GetJson(forgeJsonText);
}
if (hasNeoForge)
{
var neoForgeJsonText = ModBase.ReadFile(neoForgeJsonPath);
if (!neoForgeJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "NeoForge", neoForgeJsonPath,
neoForgeJsonText.Substring(0, Math.Min(neoForgeJsonText.Length, 1000))));
neoForgeJson = (JsonObject)ModBase.GetJson(neoForgeJsonText);
}
if (hasCleanroom)
{
var cleanroomJsonText = ModBase.ReadFile(cleanroomJsonPath);
if (!cleanroomJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "Cleanroom", cleanroomJsonPath,
cleanroomJsonText.Substring(0, Math.Min(cleanroomJsonText.Length, 1000))));
cleanroomJson = (JsonObject)ModBase.GetJson(cleanroomJsonText);
}
if (hasLiteLoader)
{
var liteLoaderJsonText = ModBase.ReadFile(liteLoaderJsonPath);
if (!liteLoaderJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "LiteLoader", liteLoaderJsonPath,
liteLoaderJsonText.Substring(0, Math.Min(liteLoaderJsonText.Length, 1000))));
liteLoaderJson = (JsonObject)ModBase.GetJson(liteLoaderJsonText);
}
if (hasFabric)
{
var fabricJsonText = ModBase.ReadFile(fabricJsonPath);
if (!fabricJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "Fabric", fabricJsonPath,
fabricJsonText.Substring(0, Math.Min(fabricJsonText.Length, 1000))));
fabricJson = (JsonObject)ModBase.GetJson(fabricJsonText);
}
if (hasLegacyFabric)
{
var legacyFabricJsonText = ModBase.ReadFile(legacyFabricJsonPath);
if (!legacyFabricJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "Legacy Fabric", fabricJsonPath,
legacyFabricJsonText.Substring(0, Math.Min(legacyFabricJsonText.Length, 1000))));
legacyFabricJson = (JsonObject)ModBase.GetJson(legacyFabricJsonText);
}
if (hasLabyMod)
{
var labyModJsonText = ModBase.ReadFile(labyModJsonPath);
if (!labyModJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "LabyMod", labyModJsonPath,
labyModJsonText.Substring(0, Math.Min(labyModJsonText.Length, 1000))));
labyModJson = (JsonObject)ModBase.GetJson(labyModJsonText);
}
#endregion
#region JSON
// 获取 minecraftArguments
var allArguments = (minecraftJson is not null ? (minecraftJson["minecraftArguments"] ?? " ").ToString() : " ") +
" " +
(labyModJson is not null ? (labyModJson["minecraftArguments"] ?? " ").ToString() : " ") +
" " +
(optiFineJson is not null ? (optiFineJson["minecraftArguments"] ?? " ").ToString() : " ") +
" " + (forgeJson is not null ? (forgeJson["minecraftArguments"] ?? " ").ToString() : " ") +
" " +
(neoForgeJson is not null ? (neoForgeJson["minecraftArguments"] ?? " ").ToString() : " ") +
" " + (liteLoaderJson is not null
? (liteLoaderJson["minecraftArguments"] ?? " ").ToString()
: " ") + " " + (cleanroomJson is not null
? (cleanroomJson["minecraftArguments"] ?? " ").ToString()
: " ");
// 分割参数字符串
var rawArguments = allArguments.Split(" ").Where(l => !string.IsNullOrEmpty(l)).Select(l => l.Trim()).ToList();
var splitArguments = new List<string>();
for (int i = 0, loopTo = rawArguments.Count - 1; i <= loopTo; i++)
if (rawArguments[i].StartsWithF("-"))
splitArguments.Add(rawArguments[i]);
else if (splitArguments.Any() && splitArguments.Last().StartsWithF("-") &&
!splitArguments.Last().Contains(" "))
splitArguments[splitArguments.Count - 1] = splitArguments.Last() + " " + rawArguments[i];
else
splitArguments.Add(rawArguments[i]);
var realArguments = splitArguments.Distinct().ToList().Join(" ");
// 合并
// 相关讨论见 #2801
if (mMCPackInfo is not null)
{
if (mMCPackInfo.isMinecraftOverrided)
{
ModBase.Log("[Download] 当前实例的 MC 核心已被修改,使用对应的 MMC 整合包参数");
outputJson = mMCPackInfo.overridedJson;
}
else
{
ModBase.Log("[Download] 存在无修改 MC 核心文件的 MMC 整合包信息,应用相关参数");
outputJson = minecraftJson;
// 合并来自 MultiMC 的 JSON
outputJson.Merge(mMCPackInfo.overridedJson);
}
}
else
{
outputJson = minecraftJson;
}
if (hasOptiFine)
{
// 合并 OptiFine
optiFineJson.Remove("releaseTime");
optiFineJson.Remove("time");
outputJson.Merge(optiFineJson);
}
if (hasForge)
if (mMCPackInfo is null || !mMCPackInfo.isForgeOverrided)
{
// 合并 Forge
forgeJson.Remove("releaseTime");
forgeJson.Remove("time");
outputJson.Merge(forgeJson);
}
if (hasNeoForge)
if (mMCPackInfo is null || !mMCPackInfo.isNeoForgeOverrided)
{
// 合并 NeoForge
neoForgeJson.Remove("releaseTime");
neoForgeJson.Remove("time");
outputJson.Merge(neoForgeJson);
}
if (hasCleanroom)
if (mMCPackInfo is null || !mMCPackInfo.isCleanroomOverrided)
{
// 合并 Cleanroom
cleanroomJson.Remove("releaseTime");
cleanroomJson.Remove("time");
outputJson.Merge(cleanroomJson);
}
if (hasLiteLoader)
{
// 合并 LiteLoader
liteLoaderJson.Remove("releaseTime");
liteLoaderJson.Remove("time");
outputJson.Merge(liteLoaderJson);
}
if (hasFabric)
if (mMCPackInfo is null || !mMCPackInfo.isFabricOverrided)
{
// 合并 Fabric
fabricJson.Remove("releaseTime");
fabricJson.Remove("time");
outputJson.Merge(fabricJson);
}
if (hasLegacyFabric)
if (mMCPackInfo is null || !mMCPackInfo.isFabricOverrided)
{
// 合并 Fabric
legacyFabricJson.Remove("releaseTime");
legacyFabricJson.Remove("time");
outputJson.Merge(legacyFabricJson);
}
if (hasLabyMod)
{
// 合并 LabyMod
labyModJson.Remove("releaseTime");
labyModJson.Remove("time");
if (outputJson is null)
outputJson = new JsonObject();
outputJson.Merge(labyModJson);
var labyModLib =
(JsonObject)Requester.FetchJson(
$"https://releases.r2.labymod.net/api/v1/libraries/{labyModChannel}.json", RequestParam.WithRetry);
var labyModCore = (JsonObject)Requester.FetchJson(
$"https://releases.r2.labymod.net/api/v1/manifest/{labyModChannel}/latest.json", RequestParam.WithRetry);
var outputLibraries = new JsonArray();
var isolatedLibraries = new Dictionary<string, bool>();
var minecraftVersion = labyModJson["_minecraftVersion"];
foreach (var Library in labyModLib["isolated_libraries"].AsArray())
if (((JsonArray)Library["versions"]).Contains(minecraftVersion))
isolatedLibraries.Add(Library["name"].ToString(), true);
foreach (var Library in labyModJson["libraries"].AsArray())
{
var regexMatchResult = Library["name"].ToString().RegexSeek(RegexPatterns.CatchLwjglInLib);
if (regexMatchResult is null ||
!isolatedLibraries.Contains(new KeyValuePair<string, bool>(regexMatchResult, true)))
outputLibraries.Add(Library.DeepClone());
}
foreach (var Library in labyModLib["libraries"].AsArray())
{
var libraryUrl = Library?["url"]?.ToString() ?? "";
outputLibraries.Add(new JsonObject
{
["name"] = Library?["name"]?.ToString(),
["downloads"] = new JsonObject
{
["artifact"] = new JsonObject
{
["path"] = libraryUrl.Substring(libraryUrl.LastIndexOfF("https://releases.r2.labymod.net/libraries/") + 42),
["sha1"] = Library?["sha1"]?.ToString(),
["size"] = Library?["size"]?.DeepClone(),
["url"] = libraryUrl
}
}
});
}
var labyModCommitReference = labyModCore["commitReference"]?.ToString() ?? "";
outputLibraries.Add(new JsonObject
{
["name"] = "net.labymod:LabyMod:4",
["downloads"] = new JsonObject
{
["artifact"] = new JsonObject
{
["path"] = "net/labymod/LabyMod/4/LabyMod-4.jar",
["sha1"] = labyModCore["sha1"]?.ToString(),
["size"] = labyModCore["size"]?.DeepClone(),
["url"] = $"https://releases.r2.labymod.net/api/v1/download/labymod4/{labyModChannel}/{labyModCommitReference}.jar"
}
}
});
outputJson["libraries"] = outputLibraries;
outputJson.Add("labymod_data", new JsonObject
{
["channelType"] = labyModChannel,
["commitReference"] = labyModCommitReference,
["version"] = labyModCore["labyModVersion"]?.ToString(),
["versionType"] = "release"
});
}
// 修改
if (realArguments is not null && !string.IsNullOrEmpty(realArguments.Replace(" ", "")))
outputJson["minecraftArguments"] = realArguments;
if (mMCPackInfo is not null && mMCPackInfo.isMcArgsEdited)
outputJson.Remove("minecraftArguments");
outputJson.Remove("_comment_");
outputJson.Remove("inheritsFrom");
outputJson.Remove("jar");
outputJson["id"] = outputName;
#endregion
#region
ModBase.WriteFile(outputJsonPath, outputJson.ToString());
if ((minecraftJar ?? "") != (outputJar ?? "")) // 可能是同一个文件
{
if (File.Exists(outputJar))
File.Delete(outputJar);
ModBase.CopyFile(minecraftJar, outputJar);
}
ModBase.Log("[Download] 实例合并 " + outputName + " 完成");
#endregion
}
#endregion
}
@@ -0,0 +1,34 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadCleanroom"
PanScroll="{Binding ElementName=PanBack}">
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
<StackPanel Orientation="Vertical" Margin="5">
<local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True"
Title="{DynamicResource Download.Version.Cleanroom.Intro.Title}" x:Name="CardTip">
<StackPanel Margin="25,40,15,20">
<TextBlock Name="LabConnect" Margin="0,0,0,11"
Text="{DynamicResource Download.Version.Cleanroom.Intro.Description}"
TextWrapping="Wrap" />
<Grid Height="35">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
</Grid.ColumnDefinitions>
<local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140"
Text="{DynamicResource Download.Version.OpenWebsite}" Padding="13,0"
Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
</Grid>
</StackPanel>
</local:MyCard>
<StackPanel Name="PanMain" Margin="20,15,20,5" Grid.IsSharedSizeScope="True" />
<local:MyCard HorizontalAlignment="Center" Margin="40,50" SnapsToDevicePixels="True" x:Name="PanLoad"
UseAnimation="False">
<local:MyLoading Text="{DynamicResource Download.Version.Cleanroom.LoadingList}" Margin="20,20,20,17" x:Name="Load"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</local:MyCard>
</StackPanel>
</local:MyScrollViewer>
</local:MyPageRight>
@@ -0,0 +1,80 @@
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using PCL.Core.App.Localization;
namespace PCL;
public partial class PageDownloadCleanroom
{
public PageDownloadCleanroom()
{
Initialized += (_, _) => LoaderInit();
Loaded += (_, _) => Init();
InitializeComponent();
BtnWeb.Click += BtnWeb_Click;
}
private void LoaderInit()
{
PageLoaderInit(Load, PanLoad, PanMain, CardTip, ModDownload.dlCleanroomListLoader, _ => Load_OnFinish());
}
private void Init()
{
PanBack.ScrollToHome();
}
private void Load_OnFinish()
{
// 结果数据化
try
{
// 归类
var dict = ModDownload.dlCleanroomListLoader.output.Value.GroupBy(d => d.Inherit)
.OrderByDescending(g => g.Key).ToDictionary(g => g.Key, g => g.ToList());
// 清空当前
PanMain.Children.Clear();
// 转化为 UI
foreach (var Pair in dict)
{
if (!Pair.Value.Any())
continue;
// 增加卡片
var newCard = new MyCard
{ Title = Pair.Key + " (" + Pair.Value.Count + ")", Margin = new Thickness(0d, 0d, 0d, 15d) };
var newStack = new StackPanel
{
Margin = new Thickness(20d, MyCard.SwapedHeight, 18d, 0d),
VerticalAlignment = VerticalAlignment.Top, RenderTransform = new TranslateTransform(0d, 0d),
Tag = Pair.Value
};
newCard.Children.Add(newStack);
newCard.SwapControl = newStack;
newCard.IsSwapped = true;
newCard.InstallMethod = stack =>
{
foreach (var item in (IEnumerable)stack.Tag)
stack.Children.Add(ModDownloadLib.CleanroomDownloadListItem(
(ModDownload.DlCleanroomListEntry)item, ModDownloadLib.CleanroomSave_Click, true));
};
PanMain.Children.Add(newCard);
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 Cleanroom 版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Error.OperationFailed"));
}
}
// 介绍栏
private void BtnWeb_Click(object sender, EventArgs e)
{
ModBase.OpenWebsite("https://cleanroommc.com/zh/");
}
}
@@ -0,0 +1,21 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadClient"
PanScroll="{Binding ElementName=PanBack}">
<Grid>
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"
x:Name="PanBack">
<StackPanel Orientation="Vertical" Margin="5">
<StackPanel Name="PanMain" Margin="20,20,20,5" Grid.IsSharedSizeScope="True" />
</StackPanel>
</local:MyScrollViewer>
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40,0" SnapsToDevicePixels="True"
x:Name="PanLoad" UseAnimation="False">
<local:MyLoading Text="{DynamicResource Download.Version.LoadingList}" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</local:MyCard>
</Grid>
</local:MyPageRight>
@@ -0,0 +1,121 @@
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using PCL.Network;
using PCL.Core.App.Localization;
namespace PCL;
public partial class PageDownloadClient
{
public PageDownloadClient()
{
Initialized += (_, _) => LoaderInit();
Loaded += (_, _) => Init();
InitializeComponent();
}
private void LoaderInit()
{
PageLoaderInit(Load, PanLoad, PanBack, null, ModDownload.dlClientListLoader, _ => Load_OnFinish());
}
private void Init()
{
PanBack.ScrollToHome();
}
private void Load_OnFinish()
{
// 结果数据化
try
{
var categoryOrder = new[]
{
McVersionCategory.Release,
McVersionCategory.Snapshot,
McVersionCategory.BeforeRelease,
McVersionCategory.AprilFools
};
var dict = categoryOrder.ToDictionary(
category => category,
_ => new List<JsonObject>()
);
var versions = (JsonArray)ModDownload.dlClientListLoader.output.Value["versions"];
foreach (JsonObject Version in versions)
{
var cat = McVersionClassifier.ClassifyVersion(Version);
dict[cat].Add(Version);
}
foreach (var category in categoryOrder)
dict[category] = dict[category]
.OrderByDescending(McVersionClassifier.GetReleaseTime)
.ToList();
PanMain.Children.Clear();
var cardInfo = new MyCard { Title = Lang.Text("Download.Version.Latest.Title"), Margin = new Thickness(0d, 0d, 0d, 15d) };
var topestVersions = new List<JsonObject>();
var release = (JsonObject)dict[McVersionCategory.Release][0].DeepClone();
release["lore"] = Lang.Text("Download.Version.Latest.Release", Lang.Date(McVersionClassifier.GetReleaseTime(release), "g"));
topestVersions.Add(release);
if (McVersionClassifier.GetReleaseTime(dict[McVersionCategory.Release][0]) < McVersionClassifier.GetReleaseTime(dict[McVersionCategory.Snapshot][0]))
{
var snapshot = (JsonObject)dict[McVersionCategory.Snapshot][0].DeepClone();
snapshot["lore"] = Lang.Text("Download.Version.Latest.Development",
Lang.Date(McVersionClassifier.GetReleaseTime(snapshot), "g"));
topestVersions.Add(snapshot);
}
var panInfo = new StackPanel
{
Margin = new Thickness(20d, MyCard.SwapedHeight, 18d, 0d), VerticalAlignment = VerticalAlignment.Top,
RenderTransform = new TranslateTransform(0d, 0d), Tag = topestVersions
};
void PutMethod(StackPanel stack)
{
foreach (var item in (IEnumerable)stack.Tag)
stack.Children.Add(ModDownloadLib.McDownloadListItem((JsonObject)item,
ModDownloadLib.McDownloadMenuSave, true));
}
;
MyCard.StackInstall(ref panInfo, PutMethod);
cardInfo.Children.Add(panInfo);
PanMain.Children.Add(cardInfo);
foreach (var Pair in dict)
{
if (!Pair.Value.Any())
continue;
var newCard = new MyCard
{ Title = McVersionClassifier.GetCategoryDisplayName(Pair.Key) + " (" + Pair.Value.Count + ")", Margin = new Thickness(0d, 0d, 0d, 15d) };
var newStack = new StackPanel
{
Margin = new Thickness(20d, MyCard.SwapedHeight, 18d, 0d),
VerticalAlignment = VerticalAlignment.Top, RenderTransform = new TranslateTransform(0d, 0d),
Tag = Pair.Value
};
newCard.Children.Add(newStack);
newCard.SwapControl = newStack;
newCard.InstallMethod = PutMethod;
newCard.IsSwapped = true;
PanMain.Children.Add(newCard);
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 MC 版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Error.OperationFailed"));
}
}
}
@@ -0,0 +1,104 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadCompFavorites">
<Grid>
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"
x:Name="PanBack">
<Grid x:Name="PanContent">
<StackPanel Orientation="Vertical" Margin="25">
<StackPanel>
<Grid Margin="0,0,0,15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<local:MySearchBox x:Name="PanSearchBox" Grid.Column="0" HintText="{DynamicResource Download.Comp.Favorites.Search.Hint}" />
<local:MyCard Margin="15,0,0,0" Height="40" CanSwap="False" Grid.Column="1">
<StackPanel Orientation="Horizontal">
<local:MyComboBox Margin="8,0,-1,0" VerticalAlignment="Center" Height="27"
x:Name="ComboTargetFav" MaxWidth="160" MinWidth="80"
DropDownWidthSync="False" />
<local:MyIconButton LogoScale="1.1" Margin="4,8,5,8" VerticalAlignment="Center"
x:Name="Btn_ManageTargetFav"
ToolTip="{DynamicResource Download.Comp.Favorites.Manage}" />
</StackPanel>
</local:MyCard>
</Grid>
<local:MyHint Text="{DynamicResource Download.Comp.Favorites.GetInfoFailed}" Theme="Yellow"
Margin="0,0,0,15" x:Name="HintGetFail"
Visibility="Collapsed" />
<StackPanel x:Name="PanContentList" />
<!--<local:MyCard x:Name="CardProjectsMod" Title="Mod" IsSwapped="True" CanSwap="True" Margin="0,0,0,15">
<StackPanel x:Name="PanProjectsMod" Margin="12,38,12,12" />
</local:MyCard>
<local:MyCard x:Name="CardProjectsModpack" Title="整合包" IsSwapped="True" CanSwap="True" Margin="0,0,0,15">
<StackPanel x:Name="PanProjectsModpack" Margin="12,38,12,12" />
</local:MyCard>-->
</StackPanel>
</StackPanel>
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40"
x:Name="CardNoContent">
<Grid Margin="20,17">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.ColumnSpan="4" Margin="0,0,0,9" HorizontalAlignment="Center"
Text="{DynamicResource Download.Comp.Favorites.Empty.Title}" FontSize="19"
UseLayoutRounding="True" SnapsToDevicePixels="True"
Foreground="{DynamicResource ColorBrush3}" />
<Rectangle Grid.Row="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" Height="2"
Fill="{DynamicResource ColorBrush3}" />
<TextBlock Grid.Row="2" Grid.ColumnSpan="4" Margin="10,15,10,5"
Text="{DynamicResource Download.Comp.Favorites.Empty.Description}"
TextWrapping="Wrap" />
</Grid>
</local:MyCard>
</Grid>
</local:MyScrollViewer>
<local:MyCard x:Name="CardSelect" Visibility="Collapsed" Opacity="0"
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="25,25,25,0" UseAnimation="False"
HasMouseAnimation="False">
<local:MyCard.RenderTransform>
<TranslateTransform x:Name="TransSelect" Y="-10" />
</local:MyCard.RenderTransform>
<TextBlock x:Name="LabSelect" Text="{DynamicResource Download.Comp.Favorites.Selection.Placeholder}"
HorizontalAlignment="Center" VerticalAlignment="Top"
Margin="9" Foreground="{DynamicResource ColorBrush2}" />
<StackPanel Orientation="Horizontal" Margin="5,28,5,5">
<local:MyIconTextButton x:Name="Btn_FavoritesCancel"
Text="{DynamicResource Download.Comp.Favorites.Cancel}"
LogoScale="1"
SvgIcon="lucide/heart-filled" />
<local:MyIconTextButton x:Name="Btn_FavoritesShare"
Text="{DynamicResource Download.Comp.Favorites.Share}"
LogoScale="1"
SvgIcon="lucide/share-2" />
<local:MyIconTextButton x:Name="Btn_FavoritesDownload"
Text="{DynamicResource Download.Comp.Favorites.Download}"
LogoScale="1"
SvgIcon="lucide/download" />
<local:MyIconTextButton x:Name="Btn_SelectCancel"
Text="{DynamicResource Download.Comp.Favorites.Deselect}"
LogoScale="0.8"
SvgIcon="lucide/download" />
</StackPanel>
</local:MyCard>
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="True"
x:Name="PanLoad" UseAnimation="False" Margin="40,50">
<local:MyLoading Text="{DynamicResource Download.Comp.Favorites.Loading}" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</local:MyCard>
</Grid>
</local:MyPageRight>
@@ -0,0 +1,915 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using PCL.Core.UI;
using PCL.Network;
using PCL.Network.Loaders;
using PCL.Core.App.Localization;
namespace PCL;
public partial class PageDownloadCompFavorites
{
private readonly List<MyListItem> compItemList = new();
private List<MyListItem> selectedItemList = new();
public PageDownloadCompFavorites()
{
loader = new ModLoader.LoaderTask<List<string>, List<ModComp.CompProject>>("CompProject Favorites",
CompFavoritesGet, LoaderInput);
Initialized += PageDownloadCompFavorites_Inited;
Loaded += PageDownloadCompFavorites_Loaded;
KeyDown += Page_KeyDown;
InitializeComponent();
{
// 这是选择收藏夹旁边那个图标按钮
// 实在不想把布局写动态代码里,但是奈何龙猫的石山没办法在 XAML 里定义 Logo 属性为已有常量值
// 还有一个很扯淡的点,同样自定义的 MyButton 能在 XAML 直接设置 Click 事件
// 到 MyIconButton 就不行了,死活跑不了,也不知道是不是漏了什么依赖属性没写
Btn_ManageTargetFav.SvgIcon = "lucide/settings";
Btn_ManageTargetFav.Click += Manage_Click;
}
// Handles
Load.StateChanged += Load_State;
Btn_FavoritesCancel.Click += Btn_FavoritesCancel_Clicked;
Btn_SelectCancel.Click += Btn_SelectCancel_Clicked;
Btn_FavoritesShare.Click += Btn_FavoritesShare_Clicked;
Btn_FavoritesDownload.Click += Btn_FavoritesDownload_Clicked;
ComboTargetFav.SelectionChanged += ComboTargetFav_Selected;
HintGetFail.MouseLeftButtonDown += HintGetFail_MouseLeftButtonDown;
PanSearchBox.TextChanged += SearchRun;
WeakLanguageChanged.Add(this, OnLanguageChanged);
}
private static void OnLanguageChanged(PageDownloadCompFavorites page) => ModBase.RunInUi(page._RefreshCategoryTitles);
private ModComp.CompFavorites.FavData CurrentFavTarget
{
get
{
var selectedItem = (MyComboBoxItem)ComboTargetFav.SelectedItem;
if (selectedItem is null)
{
ModBase.Log("[Favorites] 异常:未选择收藏夹");
selectedItem = (MyComboBoxItem)ComboTargetFav.Items.GetItemAt(0);
}
return ModComp.CompFavorites.FavoritesList
.First(e => string.Equals(e.Id, selectedItem.Tag?.ToString(), StringComparison.OrdinalIgnoreCase));
}
}
#region
// 加载器信息
public ModLoader.LoaderTask<List<string>, List<ModComp.CompProject>> loader;
private void PageDownloadCompFavorites_Inited(object sender, EventArgs e)
{
RefreshFavTargets();
PageLoaderInit(Load, PanLoad, PanContent, null, loader, _ => Load_OnFinish(), LoaderInput);
}
private void PageDownloadCompFavorites_Loaded(object sender, EventArgs e)
{
Items_SetSelectAll(false);
RefreshBar();
if (loader.input is not null && !loader.input.Count.Equals(CurrentFavTarget.Favs.Count)) RefreshFavTargets();
}
private List<string> LoaderInput()
{
List<string> targetList = null;
try
{
targetList = CurrentFavTarget.Favs.Distinct().ToList();
}
catch (Exception ex)
{
ModBase.Log(ex, "[Favorites] 加载收藏夹列表时出错");
}
return (List<string>)targetList.Clone(); // 复制而不是直接引用!
}
private void CompFavoritesGet(ModLoader.LoaderTask<List<string>, List<ModComp.CompProject>> task)
{
task.output = ModComp.CompRequest.GetCompProjectsByIds(task.input);
}
#endregion
#region UI -
public class CompListItemContainer // 用来存储自动依据类型生成的卡片及其相关信息
{
public MyCard Card { get; set; }
public StackPanel ContentList { get; set; }
public string Title { get; set; }
public int CompType { get; set; }
}
private readonly List<CompListItemContainer> itemList = new();
/// <summary>
/// 刷新收藏夹列表
/// </summary>
private void RefreshFavTargets()
{
ComboTargetFav.Items.Clear();
foreach (var Target in ModComp.CompFavorites.FavoritesList)
{
var item = new MyComboBoxItem
{
Content = Target.Name,
Tag = Target.Id
};
ComboTargetFav.Items.Add(item);
}
if (ComboTargetFav.SelectedIndex == -1) ComboTargetFav.SelectedIndex = 0; // 默认选择第一个
}
/// <summary>
/// 返回适合当前工程项目的卡片记录
/// </summary>
/// <param name="type">工程项目类型</param>
/// <returns></returns>
private CompListItemContainer GetSuitListContainer(int type)
{
if (itemList.Any(e => e.CompType.Equals(type))) return itemList.First(e => e.CompType.Equals(type));
var newItem = new CompListItemContainer
{
Card = new MyCard
{
CanSwap = true,
Margin = new Thickness(0d, 0d, 0d, 15d)
},
ContentList = new StackPanel
{
Orientation = Orientation.Vertical,
Margin = new Thickness(12d, 38d, 12d, 12d)
},
CompType = type
};
newItem.Title = _GetCategoryTitleFormat(type);
newItem.Card.Title = string.Format(newItem.Title, 0);
newItem.Card.Children.Add(newItem.ContentList);
itemList.Add(newItem);
return newItem;
}
private void RefreshContent()
{
foreach (var item in itemList) // 清除逻辑父子关系
item.ContentList.Children.Clear();
PanContentList.Children.Clear();
var dataSource = IsSearching ? searchResult : compItemList;
foreach (var item in dataSource)
GetSuitListContainer(IsSearching ? -1 : (int)((ModComp.CompProject)item.Tag).Type).ContentList.Children
.Add(item);
foreach (var item in itemList)
{
if (item.ContentList.Children.Count == 0)
continue;
PanContentList.Children.Add(item.Card);
}
}
private static string _GetCategoryTitleFormat(int type) => type switch
{
-1 => Lang.Text("Download.Comp.Favorites.SearchResults.Title"),
(int)ModComp.CompType.Mod => Lang.Text("Download.Comp.Favorites.Category.Mod"),
(int)ModComp.CompType.ModPack => $"{Lang.Text("Download.Comp.Type.Modpack")} ({{0}})",
(int)ModComp.CompType.ResourcePack => $"{Lang.Text("Download.Comp.Type.ResourcePack")} ({{0}})",
(int)ModComp.CompType.Shader => $"{Lang.Text("Download.Comp.Type.Shader")} ({{0}})",
(int)ModComp.CompType.DataPack => $"{Lang.Text("Download.Comp.Type.DataPack")} ({{0}})",
(int)ModComp.CompType.Plugin => $"{Lang.Text("Download.Comp.Type.Plugin")} ({{0}})",
(int)ModComp.CompType.World => $"{Lang.Text("Download.Comp.Type.World")} ({{0}})",
_ => $"{Lang.Text("Download.Comp.Favorites.UnknownType")} ({{0}})"
};
private void _RefreshCategoryTitles()
{
foreach (var item in itemList)
item.Title = _GetCategoryTitleFormat(item.CompType);
RefreshCardTitle();
}
private void RefreshCardTitle()
{
foreach (var item in itemList)
item.Card.Title = string.Format(item.Title,
compItemList.Where(e => (int)((ModComp.CompProject)e.Tag).Type == item.CompType).Count());
if (!itemList.Any(e => e.CompType.Equals(-1)))
return;
var searchItem = itemList.First(e => e.CompType.Equals(-1));
if (searchItem is not null) searchItem.Card.Title = string.Format(searchItem.Title, searchResult.Count);
}
#endregion
#region UI -
// 结果 UI 化
private void Load_OnFinish()
{
itemList.Clear();
try
{
allowSearch = false;
PanSearchBox.Text = string.Empty;
allowSearch = true;
compItemList.Clear();
var someGetFail = loader.input.Count != loader.output.Count;
HintGetFail.Visibility = someGetFail ? Visibility.Visible : Visibility.Collapsed;
foreach (var item in loader.output)
{
var compItem = item.ToListItem();
ListItemBuild(compItem);
compItemList.Add(compItem);
}
if (compItemList.Any()) // 有收藏
{
if (!IsSearching)
{
PanSearchBox.Visibility = Visibility.Visible;
PanContentList.Visibility = Visibility.Visible;
CardNoContent.Visibility = Visibility.Collapsed;
}
}
else // 没有收藏
{
PanSearchBox.Visibility = Visibility.Collapsed;
PanContentList.Visibility = Visibility.Collapsed;
CardNoContent.Visibility = Visibility.Visible;
}
RefreshContent();
RefreshCardTitle();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化收藏夹列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Comp.Favorites.Error.OperationFailed"));
}
}
private void ListItemBuild(MyListItem compItem)
{
compItem.Type = MyListItem.CheckType.CheckBox;
var compId = ((ModComp.CompProject)compItem.Tag).Id;
// ----备注----
var notes = "";
CurrentFavTarget.Notes.TryGetValue(compId, out notes);
var noteItem = new Run { Foreground = new SolidColorBrush(Color.FromRgb(0, 184, 148)) };
if (!string.IsNullOrWhiteSpace(notes)) noteItem.Text = $" ({notes})";
compItem.LabTitle.Inlines.Add(noteItem);
// ----添加按钮----
// 修改备注按钮
var btn_EditNote = new MyIconButton();
btn_EditNote.SvgIcon = "lucide/pencil";
btn_EditNote.ToolTip = Lang.Text("Download.Comp.Favorites.EditNote");
ToolTipService.SetPlacement(btn_EditNote, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btn_EditNote, 30d);
ToolTipService.SetHorizontalOffset(btn_EditNote, 2d);
btn_EditNote.Click += (sender, e) =>
{
CurrentFavTarget.Notes.TryGetValue(compId, out notes);
var desiredNote = ModMain.MyMsgBoxInput(Lang.Text("Download.Comp.Favorites.EditNote"), defaultInput: notes);
// 只有在用户确认时才更新备注,避免取消时清空原有备注
if (desiredNote is not null)
{
CurrentFavTarget.Notes[compId] = desiredNote;
noteItem.Text = string.IsNullOrWhiteSpace(desiredNote) ? "" : $" ({desiredNote})";
ModComp.CompFavorites.Save();
}
};
// 删除按钮
var btn_Delete = new MyIconButton
{
SvgIcon = "lucide/heart-filled",
ToolTip = Lang.Text("Download.Comp.Favorites.Action.Unfavorite")
};
ToolTipService.SetPlacement(btn_Delete, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btn_Delete, 30d);
ToolTipService.SetHorizontalOffset(btn_Delete, 2d);
btn_Delete.Click += (sender, e) =>
{
Items_CancelFavorites(compItem);
RefreshContent();
RefreshCardTitle();
RefreshBar();
};
compItem.Buttons = [btn_EditNote, btn_Delete];
// ---操作逻辑---
// 右键查看详细信息界面
if (compItem.Tag is ModComp.CompProject)
compItem.MouseRightButtonUp += (_, _) => ModMain.frmMain.PageChange(
new FormMain.PageStackData
{
page = FormMain.PageType.CompDetail,
additional = ((ModComp.CompProject)compItem.Tag, new List<string>(), string.Empty, ModComp.CompLoaderType.Any,
((ModComp.CompProject)compItem.Tag).Type, null)
});
// ---其它事件---
compItem.Changed += ItemCheckStatusChanged;
}
#endregion
#region UI -
private int bottomBarShownCount;
private void RefreshBar()
{
var newCount = selectedItemList.Count;
var selected = newCount > 0;
if (selected)
LabSelect.Text = Lang.Text("Download.Comp.Favorites.Hint.SelectedCount", newCount); // 取消所有选择时不更新数字
// 更新显示状态
if (ModAnimation.AniControlEnabled == 0)
{
PanContentList.Margin = new Thickness(0d, 0d, 0d, selected ? 80 : 0);
if (selected)
{
// 仅在数量增加时播放出现/跳跃动画
if (bottomBarShownCount >= newCount)
{
bottomBarShownCount = newCount;
return;
}
bottomBarShownCount = newCount;
// 出现/跳跃动画
CardSelect.Visibility = Visibility.Visible;
ModAnimation.AniStart(
new[]
{
ModAnimation.AaOpacity(CardSelect, 1d - CardSelect.Opacity, 60),
ModAnimation.AaTranslateY(CardSelect, -27 - TransSelect.Y, 120,
ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)),
ModAnimation.AaTranslateY(CardSelect, 3d, 150, 120,
new ModAnimation.AniEaseInoutFluent(ModAnimation.AniEasePower.Weak)),
ModAnimation.AaTranslateY(CardSelect, -1, 90, 270,
new ModAnimation.AniEaseInoutFluent(ModAnimation.AniEasePower.Weak))
}, "CompFavorites Sidebar");
}
else
{
// 不重复播放隐藏动画
if (bottomBarShownCount == 0)
return;
bottomBarShownCount = 0;
// 隐藏动画
ModAnimation.AniStart(
new[]
{
ModAnimation.AaOpacity(CardSelect, -CardSelect.Opacity, 90),
ModAnimation.AaTranslateY(CardSelect, -10 - TransSelect.Y, 90,
ease: new ModAnimation.AniEaseInFluent(ModAnimation.AniEasePower.Weak)),
ModAnimation.AaCode(() => CardSelect.Visibility = Visibility.Collapsed, after: true)
}, "CompFavorites Sidebar");
}
}
else
{
ModAnimation.AniStop("CompFavorites Sidebar");
bottomBarShownCount = newCount;
if (selected)
{
CardSelect.Visibility = Visibility.Visible;
CardSelect.Opacity = 1d;
TransSelect.Y = -25;
}
else
{
CardSelect.Visibility = Visibility.Collapsed;
CardSelect.Opacity = 0d;
TransSelect.Y = -10;
}
}
}
#endregion
#region
// 选中状态改变
private void ItemCheckStatusChanged(object sender, ModBase.RouteEventArgs e)
{
var senderItem = (MyListItem)sender;
if (selectedItemList.Contains(senderItem))
selectedItemList.Remove(senderItem);
if (senderItem.Checked)
selectedItemList.Add(senderItem);
RefreshBar();
}
// 自动重试
private void Load_State(object sender, MyLoading.MyLoadingState state, MyLoading.MyLoadingState oldState)
{
switch (loader.State)
{
case ModBase.LoadState.Failed:
{
var errorMessage = "";
if (loader.Error is not null)
errorMessage = loader.Error.Message;
if (errorMessage.Contains(Lang.Text("Common.Error.InvalidJson")))
{
ModBase.Log("[Download] 下载的工程列表 JSON 文件损坏,已自动重试", ModBase.LogLevel.Debug);
PageLoaderRestart();
}
break;
}
}
}
private void Btn_FavoritesCancel_Clicked(object sender, ModBase.RouteEventArgs e)
{
foreach (var Items in selectedItemList.Clone())
Items_CancelFavorites(Items);
if (compItemList.Any())
{
RefreshContent();
RefreshCardTitle();
}
else
{
loader.Start();
}
RefreshBar();
}
private void Btn_SelectCancel_Clicked(object sender, ModBase.RouteEventArgs e)
{
Items_SetSelectAll(false);
}
private void Btn_FavoritesShare_Clicked(object sender, ModBase.RouteEventArgs e)
{
try
{
ModBase.ClipboardSet(
ModComp.CompFavorites.GetShareCode(selectedItemList.Select(i => ((ModComp.CompProject)i.Tag).Id)
.ToHashSet()));
Items_SetSelectAll(false);
}
catch (Exception ex)
{
ModBase.Log(
ex,
"[CompFavourites] 分享收藏时发生错误",
ModBase.LogLevel.Hint,
userSummary: Lang.Text("Download.Comp.Favorites.Error.OperationFailed"));
}
}
private void Btn_FavoritesDownload_Clicked(object sender, ModBase.RouteEventArgs e)
{
try
{
if (1 != ModMain.MyMsgBox(
Lang.Text("Download.Comp.Favorites.Dialog.BulkDownload.Content"),
Lang.Text("Download.Comp.Favorites.Dialog.BulkDownload.Title"),
Lang.Text("Common.Action.Continue"),
Lang.Text("Common.Action.Cancel"), isWarn: true
)) return;
var supportedModLoader = new List<ModComp.CompLoaderType>();
var loaderFirstSet = true;
var hasMod = false;
foreach (var Item in selectedItemList) // 获取共同支持的 ModLoader
{
var proj = (ModComp.CompProject)Item.Tag;
if (proj.Type == ModComp.CompType.Mod)
{
hasMod = true;
if (loaderFirstSet)
{
loaderFirstSet = false;
supportedModLoader = proj.ModLoaders;
}
else
{
supportedModLoader = supportedModLoader.Intersect(proj.ModLoaders).ToList();
}
}
}
// 检查是否有共同支持的 ModLoader
if (hasMod && supportedModLoader.Count == 0)
{
HintService.Hint(Lang.Text("Download.Comp.Favorites.Hint.SelectLoader"), HintType.Error);
return;
}
// 要求选择版本
var desiredModLoader = ModComp.CompLoaderType.Any;
if (hasMod && supportedModLoader.Count > 0)
if (supportedModLoader.Count > 0)
{
var mSelection = new List<IMyRadio>();
foreach (var i in supportedModLoader)
mSelection.Add(new MyRadioBox { Text = i.ToString() });
var selectedModLoaderStr = ModMain.MyMsgBoxSelect(mSelection, Lang.Text("Download.Comp.Favorites.Dialog.SelectLoader.Title"), button2: Lang.Text("Common.Action.Cancel"));
if (selectedModLoaderStr is null)
return;
desiredModLoader = supportedModLoader[(int)selectedModLoaderStr];
}
HintService.Hint(Lang.Text("Download.Comp.Favorites.Hint.LoadingVersions"));
// 输入 Ids,输出合适版本
var getInfoAndDownloadLoader = new List<ModLoader.LoaderBase>();
getInfoAndDownloadLoader.Add(new ModLoader.LoaderTask<List<string>, List<DownloadFile>>(
Lang.Text("Download.Comp.Favorites.LoaderName.QueryInfo"), ts =>
{
List<List<ModComp.CompFile>> allFiles = [];
List<string> suitVersion = [];
var versionFirstSet = true;
// 工程支持的全部版本获取
Func<List<List<string>>, List<string>> getAllVersionList = ls =>
{
var allVersionList = new List<string>();
foreach (var i in ls) allVersionList.AddRange(i);
return allVersionList.Distinct().ToList();
};
// 获取多个工程之间支持的版本的交集
var finishedTasks = 0;
foreach (var Item in ts.input)
ModBase.RunInNewThread(() =>
{
try
{
allFiles.Add(ModComp.CompFilesGet(Item, ModComp.CompRequest.IsFromCurseForge(Item))
.Where(i => i.Type != ModComp.CompType.Mod || i.ModLoaders.Contains(desiredModLoader))
.ToList());
}
catch (Exception ex)
{
ModBase.Log(
ex,
$"获取 {Item} 的下载信息失败",
ModBase.LogLevel.Hint,
userSummary: Lang.Text("Download.Comp.Favorites.Error.OperationFailed"));
}
finally
{
finishedTasks += 1;
}
});
while (finishedTasks != ts.input.Count)
Thread.Sleep(200);
// 求取共同的版本
foreach (var Item in allFiles)
{
var current = getAllVersionList(Item.Select(i => i.GameVersions).ToList());
if (versionFirstSet)
{
versionFirstSet = false;
suitVersion = current;
}
else
{
suitVersion = suitVersion.Intersect(current).ToList();
}
// Log(SuitVersion.Join(","))
if (suitVersion.Count == 0)
{
HintService.Hint(Lang.Text("Download.Comp.Favorites.Hint.NoResource"), HintType.Error);
ts.Abort();
return;
}
// 要求用户选择希望下载的版本
}
int? selectedVersion = 0;
ModBase.RunInUiWait(() =>
{
List<IMyRadio> selection = [];
foreach (var i in suitVersion)
selection.Add(new MyRadioBox { Text = i });
selectedVersion = ModMain.MyMsgBoxSelect(selection, Lang.Text("Download.Comp.Favorites.Dialog.SelectVersion.Title"), button2: Lang.Text("Common.Action.Cancel"));
if (selectedVersion is null) ts.Abort();
});
string selectedVersionStr = suitVersion[(int)selectedVersion];
HintService.Hint(Lang.Text("Download.Comp.Favorites.Hint.SelectSaveLocation", selectedVersionStr));
var saveFolder = SystemDialogs.SelectFolder();
if (string.IsNullOrWhiteSpace(saveFolder))
{
ts.Abort();
return;
}
;
// 获取有期望版本号的文件
List<DownloadFile> res = [];
foreach (var Target in allFiles)
{
// 按照发布日期排序
var finalChoices = Target.Where(i => i.GameVersions.Contains(selectedVersionStr)).ToList();
finalChoices.Sort((a, b) => a.ReleaseDate > b.ReleaseDate);
// 获取文件名
var targetProject = ModComp.compProjectCache[finalChoices.First().ProjectId];
var fileName = ModComp.CompFileNameGet(targetProject, finalChoices.First());
// 选择最新版本进行下载
res.Add(finalChoices.First().ToNetFile(System.IO.Path.Combine(saveFolder, fileName)));
}
ts.output = res;
})
{
ProgressWeight = 2d
});
getInfoAndDownloadLoader.Add(
new LoaderDownload(
Lang.Text("Download.Comp.Favorites.LoaderName.BatchDownloadSuitable"),
[]
)
{
ProgressWeight = 8d
}
);
var checkLoader = new ModLoader.LoaderCombo<List<string>>(
Lang.Text("Download.Comp.Favorites.LoaderName.BatchDownload", ModBase.GetUuid()),
getInfoAndDownloadLoader
)
{
OnStateChanged = ModDownloadLib.LoaderStateChangedHintOnly
};
checkLoader.Start(selectedItemList.Select(i => ((ModComp.CompProject)i.Tag).Id).ToList());
ModLoader.LoaderTaskbarAdd(checkLoader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
Items_SetSelectAll(false);
}
catch (Exception ex)
{
ModBase.Log(
ex,
"批量下载收藏时发生错误",
ModBase.LogLevel.Hint,
userSummary: Lang.Text("Download.Comp.Favorites.Error.OperationFailed"));
}
}
private void Items_SetSelectAll(bool targetStatus)
{
if (IsSearching)
foreach (var Item in searchResult)
Item.Checked = targetStatus;
else
foreach (var Item in compItemList)
Item.Checked = targetStatus;
selectedItemList = compItemList.Where(e => e.Checked).ToList();
}
private void Items_CancelFavorites(MyListItem item)
{
try
{
compItemList.Remove(item);
if (selectedItemList.Contains(item))
selectedItemList.Remove(item);
if (searchResult.Contains(item))
searchResult.Remove(item);
CurrentFavTarget.Favs.Remove(((ModComp.CompProject)item.Tag).Id);
ModComp.CompFavorites.Save();
if (!compItemList.Any())
ModMain.frmDownloadCompFavorites.PageLoaderRestart();
}
catch (Exception ex)
{
ModBase.Log(ex, "[CompFavourites] 移除收藏时发生错误");
}
}
private void Page_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.A && (e.KeyboardDevice.IsKeyDown(Key.LeftCtrl) || e.KeyboardDevice.IsKeyDown(Key.RightCtrl)))
Items_SetSelectAll(true);
}
private void Manage_Click(object sender, EventArgs _)
{
var body = new ContextMenu();
var newItem = new MyMenuItem
{
Header = Lang.Text("Download.Comp.Favorites.Menu.Share"),
SvgIcon = "lucide/share-2"
};
newItem.Click += (_, _) =>
{
try
{
if (CurrentFavTarget.Favs.Count == 0)
{
HintWrapper.Show(Lang.Text("Download.Comp.Favorites.Hint.NothingShared"));
return;
}
ModBase.ClipboardSet(ModComp.CompFavorites.GetShareCode(CurrentFavTarget.Favs));
}
catch (Exception ex)
{
ModBase.Log(
ex,
"[Favourites] 分享收藏时发生错误",
ModBase.LogLevel.Hint,
userSummary: Lang.Text("Download.Comp.Favorites.Error.OperationFailed"));
}
};
body.Items.Add(newItem);
newItem = new MyMenuItem
{
Header = Lang.Text("Download.Comp.Favorites.Menu.Import"),
SvgIcon = "lucide/circle-plus"
};
newItem.Click += (_, _) =>
{
try
{
var clipData = ModMain.MyMsgBoxInput(Lang.Text("Download.Comp.Favorites.Dialog.Import.Input"), hintText: Lang.Text("Download.Comp.Favorites.Dialog.Import.Hint"));
if (string.IsNullOrWhiteSpace(clipData)) return;
var newFavs = ModComp.CompFavorites.GetIdsByShareCode(clipData);
if (newFavs.Count == 0)
{
HintService.Hint(Lang.Text("Download.Comp.Favorites.Hint.NothingShared"));
return;
}
var userWant = ModMain.MyMsgBox(Lang.Text("Download.Comp.Favorites.Dialog.Import.Type"), button1: Lang.Text("Download.Comp.Favorites.Dialog.Import.NewFolder"), button2: Lang.Text("Download.Comp.Favorites.Dialog.Import.CurrentFolder"));
switch (userWant)
{
case 1:
{
var newFavName = ModMain.MyMsgBoxInput(Lang.Text("Download.Comp.Favorites.Dialog.Import.New"), Lang.Text("Download.Comp.Favorites.Dialog.NewPrompt"));
if (string.IsNullOrWhiteSpace(newFavName)) return;
ModComp.CompFavorites.FavoritesList.Add(ModComp.CompFavorites.GetNewFav(newFavName, newFavs));
ModComp.CompFavorites.Save();
RefreshFavTargets();
ComboTargetFav.SelectedIndex = ComboTargetFav.Items.Count - 1;
break;
}
case 2:
{
newFavs.ToList().ForEach(x => CurrentFavTarget.Favs.Add(x));
ModComp.CompFavorites.Save();
loader.Start(isForceRestart: true);
break;
}
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"解析分享数据失败",
ModBase.LogLevel.Hint,
userSummary: Lang.Text("Download.Comp.Favorites.Error.OperationFailed"));
}
};
body.Items.Add(newItem);
newItem = new MyMenuItem
{
Header = Lang.Text("Download.Comp.Favorites.Menu.New"),
SvgIcon = "lucide/folder-plus"
};
newItem.Click += (_, _) =>
{
var newFavName = ModMain.MyMsgBoxInput(Lang.Text("Download.Comp.Favorites.Menu.New"), Lang.Text("Download.Comp.Favorites.Dialog.NewPrompt"));
if (string.IsNullOrWhiteSpace(newFavName))
return;
ModComp.CompFavorites.FavoritesList.Add(ModComp.CompFavorites.GetNewFav(newFavName, null));
ModComp.CompFavorites.Save();
RefreshFavTargets();
ComboTargetFav.SelectedIndex = ComboTargetFav.Items.Count - 1;
};
body.Items.Add(newItem);
newItem = new MyMenuItem
{
Header = Lang.Text("Download.Comp.Favorites.Menu.Rename"),
SvgIcon = "lucide/pencil"
};
newItem.Click += (_, _) =>
{
var newName = ModMain.MyMsgBoxInput(Lang.Text("Download.Comp.Favorites.Dialog.Rename.Title"), defaultInput: CurrentFavTarget.Name);
if (string.IsNullOrWhiteSpace(newName) || (CurrentFavTarget.Name ?? "") == (newName ?? ""))
return;
CurrentFavTarget.Name = newName;
ModComp.CompFavorites.Save();
RefreshFavTargets();
};
body.Items.Add(newItem);
newItem = new MyMenuItem
{
Header = Lang.Text("Download.Comp.Favorites.Menu.Delete"),
SvgIcon = "lucide/trash-2"
};
newItem.Click += (_, _) =>
{
if (ModComp.CompFavorites.FavoritesList.Count == 1)
{
HintService.Hint(Lang.Text("Download.Comp.Favorites.Hint.LastCollection"));
return;
}
var content = Lang.Text("Download.Comp.Favorites.Dialog.Delete.Confirm", CurrentFavTarget.Name, CurrentFavTarget.Favs.Count, CurrentFavTarget.Id);
var res = ModMain.MyMsgBox(content, Lang.Text("Download.Comp.Favorites.Dialog.Delete.Title"), isWarn: true, button1: Lang.Text("Common.Option.No"), button2: Lang.Text("Common.Option.Yes"), button3: Lang.Text("Common.Option.No"));
if (res == 2)
{
ModComp.CompFavorites.FavoritesList.Remove(CurrentFavTarget);
ModComp.CompFavorites.Save();
HintService.Hint(Lang.Text("Download.Comp.Favorites.Hint.Deleted"), HintType.Success);
RefreshFavTargets();
ComboTargetFav.SelectedIndex = 0;
}
};
body.Items.Add(newItem);
body.PlacementTarget = (UIElement)sender;
body.Placement = PlacementMode.Bottom;
body.IsOpen = true;
}
private void ComboTargetFav_Selected(object sender, RoutedEventArgs e)
{
if (ComboTargetFav.SelectedItem is null)
return;
Items_SetSelectAll(false);
loader.Start(isForceRestart: true);
}
private void HintGetFail_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var content = Lang.Text("Download.Comp.Favorites.Dialog.GetFailed.Content") + "\r\n" + "\r\n";
var failIds = loader.input.Except(loader.output.Select(i => i.Id).ToList()).ToList();
foreach (var Id in failIds)
content += $" - {Id}" + "\r\n";
ModMain.MyMsgBox(content, Lang.Text("Download.Comp.Favorites.Dialog.GetFailed.Title"), button2: Lang.Text("Download.Comp.Favorites.Dialog.GetFailed.CopyIds"), button3: Lang.Text("Download.Comp.Favorites.Dialog.GetFailed.Remove"),
button2Action: () => ModBase.ClipboardSet(failIds.Join("\r\n")), button3Action: () =>
{
foreach (var Id in failIds)
CurrentFavTarget.Favs.Remove(Id);
ModComp.CompFavorites.Save();
HintService.Hint(Lang.Text("Download.Comp.Favorites.Hint.Removed"), HintType.Success);
});
}
#endregion
#region
private bool IsSearching => !string.IsNullOrWhiteSpace(PanSearchBox.Text);
private bool allowSearch = true;
private List<MyListItem> searchResult = new();
public void SearchRun(object sender, EventArgs e)
{
if (!allowSearch)
return;
if (IsSearching)
{
// 构造请求
var queryList = new List<ModBase.SearchEntry<MyListItem>>();
foreach (var Item in compItemList)
{
if (Item.Tag is not ModComp.CompProject)
continue;
var entry = (ModComp.CompProject)Item.Tag;
var searchSource = new List<ModBase.SearchSource>();
searchSource.Add(new ModBase.SearchSource(entry.RawName, 1d));
if (entry.Description is not null && !string.IsNullOrEmpty(entry.Description))
searchSource.Add(new ModBase.SearchSource(entry.Description, 0.4d));
if ((entry.TranslatedName ?? "") != (entry.RawName ?? ""))
searchSource.Add(new ModBase.SearchSource(entry.TranslatedName, 1d));
searchSource.Add(new ModBase.SearchSource(string.Join("", entry.Tags), 0.2d));
queryList.Add(new ModBase.SearchEntry<MyListItem> { item = Item, searchSource = searchSource });
}
// 进行搜索
searchResult = ModBase.Search(queryList, PanSearchBox.Text, ModBase.MaxLocalSearchDepth, 0.35d).Select(r => r.item).ToList();
}
RefreshContent();
RefreshCardTitle();
}
#endregion
}
@@ -0,0 +1,37 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadFabric"
PanScroll="{Binding ElementName=PanBack}">
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
<StackPanel Orientation="Vertical" Margin="5">
<local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True"
Title="{DynamicResource Download.Version.Fabric.Intro.Title}" x:Name="CardTip">
<StackPanel Margin="25,40,15,20">
<TextBlock Margin="0,0,0,11"
Text="{DynamicResource Download.Version.Fabric.Intro.Description}"
TextWrapping="Wrap" />
<Grid Height="35">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
</Grid.ColumnDefinitions>
<local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140"
Text="{DynamicResource Download.Version.OpenWebsite}" Padding="13,0"
Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
</Grid>
</StackPanel>
</local:MyCard>
<local:MyCard x:Name="CardVersions" Margin="20,15,20,20"
Title="{DynamicResource Download.Version.VersionList}">
<StackPanel x:Name="PanVersions" Margin="20,40,18,20" />
</local:MyCard>
<local:MyCard HorizontalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False"
Margin="40,50">
<local:MyLoading Text="{DynamicResource Download.Version.Fabric.LoadingList}" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</local:MyCard>
</StackPanel>
</local:MyScrollViewer>
</local:MyPageRight>
@@ -0,0 +1,57 @@
using PCL.Core.App.Localization;
namespace PCL;
public partial class PageDownloadFabric
{
public PageDownloadFabric()
{
Initialized += (_, _) => LoaderInit();
Loaded += (_, _) => Init();
InitializeComponent();
BtnWeb.Click += BtnWeb_Click;
}
private void LoaderInit()
{
PageLoaderInit(Load, PanLoad, CardVersions, CardTip, ModDownload.dlFabricListLoader, _ => Load_OnFinish());
}
private void Init()
{
PanBack.ScrollToHome();
}
private void Load_OnFinish()
{
// 结果数据化
try
{
var versions = (JsonArray)ModDownload.dlFabricListLoader.output.Value["installer"];
PanVersions.Children.Clear();
foreach (var Version in versions)
PanVersions.Children.Add(
ModDownloadLib.FabricDownloadListItem((JsonObject)Version,
(sender, e) => Fabric_Selected((MyListItem)sender, e)));
CardVersions.Title = Lang.Text("Download.Version.VersionListCount", versions.Count);
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 Fabric 版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Error.OperationFailed"));
}
}
private void Fabric_Selected(MyListItem sender, EventArgs e)
{
ModDownloadLib.McDownloadFabricLoaderSave((JsonObject)sender.Tag);
}
private void BtnWeb_Click(object sender, EventArgs e)
{
ModBase.OpenWebsite("https://www.fabricmc.net");
}
}
@@ -0,0 +1,33 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadForge"
PanScroll="{Binding ElementName=PanBack}">
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
<StackPanel Orientation="Vertical" Margin="5">
<local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True"
Title="{DynamicResource Download.Version.Forge.Intro.Title}" x:Name="CardTip">
<StackPanel Margin="25,40,15,20">
<TextBlock Name="LabConnect" Margin="0,0,0,11"
Text="{DynamicResource Download.Version.Forge.Intro.Description}" TextWrapping="Wrap" />
<Grid Height="35">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
</Grid.ColumnDefinitions>
<local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140"
Text="{DynamicResource Download.Version.OpenWebsite}" Padding="13,0"
Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
</Grid>
</StackPanel>
</local:MyCard>
<StackPanel Name="PanMain" Margin="20,15,20,5" Grid.IsSharedSizeScope="True" />
<local:MyCard HorizontalAlignment="Center" Margin="40,50" SnapsToDevicePixels="True" x:Name="PanLoad"
UseAnimation="False">
<local:MyLoading Text="{DynamicResource Download.Version.Forge.LoadingList}" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</local:MyCard>
</StackPanel>
</local:MyScrollViewer>
</local:MyPageRight>
@@ -0,0 +1,114 @@
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using PCL.Core.App.Localization;
namespace PCL;
public partial class PageDownloadForge
{
public PageDownloadForge()
{
Initialized += (_, _) => LoaderInit();
Loaded += (_, _) => Init();
InitializeComponent();
BtnWeb.Click += BtnWeb_Click;
}
private void LoaderInit()
{
PageLoaderInit(Load, PanLoad, PanMain, CardTip, ModDownload.dlForgeListLoader, _ => Load_OnFinish());
}
private void Init()
{
PanBack.ScrollToHome();
}
private void Load_OnFinish()
{
// 结果数据化
try
{
// 清空当前
PanMain.Children.Clear();
// 转化为 UI
foreach (var Version in ModDownload.dlForgeListLoader.output.Value.Sort(McVersionComparer.CompareVersionGe))
{
// 增加卡片
var newCard = new MyCard
{ Title = Version.Replace("_p", " P"), Margin = new Thickness(0d, 0d, 0d, 15d) };
var newStack = new StackPanel
{
Margin = new Thickness(20d, MyCard.SwapedHeight, 18d, 0d),
VerticalAlignment = VerticalAlignment.Top, RenderTransform = new TranslateTransform(0d, 0d),
Tag = Version
};
newCard.Children.Add(newStack);
newCard.SwapControl = newStack;
newCard.InstallMethod = stack =>
{
var loadingPickaxe = new MyLoading { Text = Lang.Text("Download.Version.Forge.LoadingList"), Margin = new Thickness(5d) };
var loader =
new ModLoader.LoaderTask<string, List<ModDownload.DlForgeVersionEntry>>("DlForgeVersion Main",
ModDownload.DlForgeVersionMain);
loadingPickaxe.State = loader;
loader.Start(stack.Tag);
loadingPickaxe.StateChanged += (a, b, c) =>
ModMain.frmDownloadForge.Forge_StateChanged((MyLoading)a, b, c);
loadingPickaxe.Click += (a, b) => ModMain.frmDownloadForge.Forge_Click((MyLoading)a, b);
stack.Children.Add(loadingPickaxe);
};
newCard.IsSwapped = true;
PanMain.Children.Add(newCard);
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 Forge 版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Error.OperationFailed"));
}
}
// Forge 版本列表加载
public void Forge_Click(MyLoading sender, MouseButtonEventArgs e)
{
if (sender.State.LoadingState == MyLoading.MyLoadingState.Error)
((ModLoader.LoaderTask<string, List<ModDownload.DlForgeVersionEntry>>)sender.State).Start(
isForceRestart: true);
}
public void Forge_StateChanged(MyLoading sender, MyLoading.MyLoadingState newState,
MyLoading.MyLoadingState oldState)
{
if (newState != MyLoading.MyLoadingState.Stop)
return;
var card = (MyCard)((FrameworkElement)sender.Parent).Parent;
var loader = (ModLoader.LoaderTask<string, List<ModDownload.DlForgeVersionEntry>>)sender.State;
// 载入列表
((StackPanel)card.SwapControl).Children.Clear();
((StackPanel)card.SwapControl).Tag = loader.output;
card.InstallMethod = stack =>
{
stack.Tag = ((List<ModDownload.DlForgeVersionEntry>)stack.Tag).Sort((a, b) => a.version > b.version);
ModDownloadLib.ForgeDownloadListItemPreload(stack, (List<ModDownload.DlForgeVersionEntry>)stack.Tag,
ModDownloadLib.ForgeSave_Click, true);
foreach (var item in (IEnumerable)stack.Tag)
stack.Children.Add(ModDownloadLib.ForgeDownloadListItem((ModDownload.DlForgeVersionEntry)item,
ModDownloadLib.ForgeSave_Click, true));
};
card.StackInstall();
}
// 介绍栏
private void BtnWeb_Click(object sender, EventArgs e)
{
ModBase.OpenWebsite("https://files.minecraftforge.net");
}
}
@@ -0,0 +1,393 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadInstall">
<Grid>
<Grid Name="PanAllBack">
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"
x:Name="PanBack">
<Grid Margin="25,10,25,25" Name="PanInner">
<StackPanel Grid.Row="1" Name="PanMinecraft" Grid.IsSharedSizeScope="True" Margin="0,0,0,-10">
<StackPanel.RenderTransform>
<TranslateTransform />
</StackPanel.RenderTransform>
</StackPanel>
<StackPanel Grid.Row="1" x:Name="PanSelect" Visibility="Collapsed" Opacity="0"
IsHitTestVisible="False" Margin="0,0,0,45">
<StackPanel.RenderTransform>
<TranslateTransform X="40" />
</StackPanel.RenderTransform>
<local:MyCard Margin="0,15,0,15" UseAnimation="False" Height="62">
<local:MyIconButton HorizontalAlignment="Left" Theme="Black" Opacity="0.6" x:Name="BtnBack"
Margin="13,0,0,0" Height="26" Width="26"
LogoScale="0.87"
SvgIcon="lucide/arrow-left" />
<local:MyImage x:Name="ImgLogo" Margin="52,0,0,0" VerticalAlignment="Center"
HorizontalAlignment="Left" Height="32"
RenderOptions.BitmapScalingMode="HighQuality" />
<local:MyTextBox Margin="93,0,16,0" Padding="-1,0" MinHeight="30" FontSize="15"
VerticalAlignment="Center" x:Name="TextSelectName" MaxLength="70"
HasBackground="False" />
</local:MyCard>
<local:MyHint Text="{DynamicResource Download.Install.Warning.FabricApi}" Margin="0,10,0,0" x:Name="HintFabricAPI"
Theme="Red" />
<local:MyHint Text="{DynamicResource Download.Install.Warning.LegacyFabricApi}" Margin="0,10,0,0"
x:Name="HintLegacyFabricAPI" Theme="Red" />
<local:MyHint Text="{DynamicResource Download.Install.Warning.OptiFabric}" Margin="0,10,0,0" x:Name="HintOptiFabric"
Theme="Red" />
<local:MyHint Text="{DynamicResource Download.Install.Warning.OptiFabricOld}"
Margin="0,10,0,0" x:Name="HintOptiFabricOld" Theme="Yellow" />
<local:MyHint Text="{DynamicResource Download.Install.Warning.LegacyOptiFabric}"
Margin="0,10,0,0" x:Name="HintLegacyOptiFabric" Theme="Yellow" />
<local:MyHint Text="{DynamicResource Download.Install.Warning.ModOptiFine}" Margin="0,10,0,0" x:Name="HintModOptiFine"
Theme="Yellow" />
<local:MyCard Title="{DynamicResource Common.Installation.Forge}" Height="40" Margin="0,12,0,0" x:Name="CardForge" IsSwapped="True"
CanSwap="True" SwapLogoRight="True">
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanForge" />
<Grid x:Name="PanForgeInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
Tag="True">
<Grid.RenderTransform>
<TranslateTransform />
</Grid.RenderTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Image x:Name="ImgForge" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
RenderOptions.BitmapScalingMode="Linear"
Source="pack://application:,,,/images/Blocks/Anvil.png" />
<TextBlock x:Name="LabForge" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Grid.Column="1" />
</Grid>
<Grid x:Name="BtnForgeClear" Height="30" Width="30" HorizontalAlignment="Right"
VerticalAlignment="Top" Margin="0,5,32,0"
Background="{StaticResource ColorBrushSemiTransparent}">
<Path x:Name="BtnForgeClearInner" Height="10" Width="10" Stretch="Uniform"
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
</Grid>
</local:MyCard>
<local:MyCard Title="{DynamicResource Common.Installation.Cleanroom}" Height="40" Margin="0,15,0,0" x:Name="CardCleanroom"
IsSwapped="True" CanSwap="True" SwapLogoRight="True">
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanCleanroom" />
<Grid x:Name="PanCleanroomInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
Tag="True">
<Grid.RenderTransform>
<TranslateTransform />
</Grid.RenderTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Image x:Name="ImgCleanroom" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
RenderOptions.BitmapScalingMode="Linear"
Source="pack://application:,,,/images/Blocks/Cleanroom.png" />
<TextBlock x:Name="LabCleanroom" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Grid.Column="1" />
</Grid>
<Grid x:Name="BtnCleanroomClear" Height="30" Width="30" HorizontalAlignment="Right"
VerticalAlignment="Top" Margin="0,5,32,0"
Background="{StaticResource ColorBrushSemiTransparent}">
<Path x:Name="BtnCleanroomClearInner" Height="10" Width="10" Stretch="Uniform"
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
</Grid>
</local:MyCard>
<local:MyCard Title="{DynamicResource Common.Installation.NeoForge}" Height="40" Margin="0,12,0,0" x:Name="CardNeoForge"
IsSwapped="True" CanSwap="True" SwapLogoRight="True">
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanNeoForge" />
<Grid x:Name="PanNeoForgeInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
Tag="True">
<Grid.RenderTransform>
<TranslateTransform />
</Grid.RenderTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Image x:Name="ImgNeoForge" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
RenderOptions.BitmapScalingMode="Linear"
Source="pack://application:,,,/images/Blocks/NeoForge.png" />
<TextBlock x:Name="LabNeoForge" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Grid.Column="1" />
</Grid>
<Grid x:Name="BtnNeoForgeClear" Height="30" Width="30" HorizontalAlignment="Right"
VerticalAlignment="Top" Margin="0,5,32,0"
Background="{StaticResource ColorBrushSemiTransparent}">
<Path x:Name="BtnNeoForgeClearInner" Height="10" Width="10" Stretch="Uniform"
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
</Grid>
</local:MyCard>
<local:MyCard Title="{DynamicResource Common.Installation.Fabric}" Height="40" Margin="0,12,0,0" x:Name="CardFabric" IsSwapped="True"
CanSwap="True" SwapLogoRight="True">
<StackPanel Margin="20,40,18,0" VerticalAlignment="Top" Name="PanFabric" />
<Grid x:Name="PanFabricInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
Tag="True">
<Grid.RenderTransform>
<TranslateTransform />
</Grid.RenderTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Image x:Name="ImgFabric" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
RenderOptions.BitmapScalingMode="Linear"
Source="pack://application:,,,/images/Blocks/Fabric.png" />
<TextBlock x:Name="LabFabric" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Grid.Column="1" />
</Grid>
<Grid x:Name="BtnFabricClear" Height="30" Width="30" HorizontalAlignment="Right"
VerticalAlignment="Top" Margin="0,5,32,0"
Background="{StaticResource ColorBrushSemiTransparent}">
<Path x:Name="BtnFabricClearInner" Height="10" Width="10" Stretch="Uniform"
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
</Grid>
</local:MyCard>
<local:MyCard Title="{DynamicResource Common.Installation.LegacyFabric}" Height="40" Margin="0,12,0,0" x:Name="CardLegacyFabric"
IsSwapped="True" CanSwap="True" SwapLogoRight="True">
<StackPanel Margin="20,40,18,0" VerticalAlignment="Top" Name="PanLegacyFabric" />
<Grid x:Name="PanLegacyFabricInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
Tag="True">
<Grid.RenderTransform>
<TranslateTransform />
</Grid.RenderTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Image x:Name="ImgLegacyFabric" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
RenderOptions.BitmapScalingMode="Linear"
Source="pack://application:,,,/images/Blocks/Fabric.png" />
<TextBlock x:Name="LabLegacyFabric" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Grid.Column="1" />
</Grid>
<Grid x:Name="BtnLegacyFabricClear" Height="30" Width="30" HorizontalAlignment="Right"
VerticalAlignment="Top" Margin="0,5,32,0"
Background="{StaticResource ColorBrushSemiTransparent}">
<Path x:Name="BtnLegacyFabricClearInner" Height="10" Width="10" Stretch="Uniform"
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
</Grid>
</local:MyCard>
<local:MyCard Title="{DynamicResource Common.Installation.FabricApi}" Height="40" Margin="0,12,0,0" x:Name="CardFabricApi"
IsSwapped="True" CanSwap="True" SwapLogoRight="True">
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanFabricApi" />
<Grid x:Name="PanFabricApiInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
Tag="True">
<Grid.RenderTransform>
<TranslateTransform />
</Grid.RenderTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Image x:Name="ImgFabricApi" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
RenderOptions.BitmapScalingMode="Linear"
Source="pack://application:,,,/images/Blocks/Fabric.png" />
<TextBlock x:Name="LabFabricApi" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Grid.Column="1" />
</Grid>
<Grid x:Name="BtnFabricApiClear" Height="30" Width="30" HorizontalAlignment="Right"
VerticalAlignment="Top" Margin="0,5,32,0"
Background="{StaticResource ColorBrushSemiTransparent}">
<Path x:Name="BtnFabricApiClearInner" Height="10" Width="10" Stretch="Uniform"
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
</Grid>
</local:MyCard>
<local:MyCard Title="{DynamicResource Common.Installation.LegacyFabricApi}" Height="40" Margin="0,12,0,0"
x:Name="CardLegacyFabricApi" IsSwapped="True" CanSwap="True" SwapLogoRight="True">
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanLegacyFabricApi" />
<Grid x:Name="PanLegacyFabricApiInfo" Height="18" Margin="132,11,15,0"
VerticalAlignment="Top" Tag="True">
<Grid.RenderTransform>
<TranslateTransform />
</Grid.RenderTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Image x:Name="ImgLegacyFabricApi" Margin="0,0,7,0" SnapsToDevicePixels="True"
Height="18" RenderOptions.BitmapScalingMode="Linear"
Source="pack://application:,,,/images/Blocks/Fabric.png" />
<TextBlock x:Name="LabLegacyFabricApi" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Grid.Column="1" />
</Grid>
<Grid x:Name="BtnLegacyFabricApiClear" Height="30" Width="30" HorizontalAlignment="Right"
VerticalAlignment="Top" Margin="0,5,32,0"
Background="{StaticResource ColorBrushSemiTransparent}">
<Path x:Name="BtnLegacyFabricApiClearInner" Height="10" Width="10" Stretch="Uniform"
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
</Grid>
</local:MyCard>
<local:MyCard Title="{DynamicResource Common.Installation.LabyMod}" Height="40" Margin="0,15,0,0" x:Name="CardLabyMod"
IsSwapped="True" CanSwap="True" SwapLogoRight="True">
<StackPanel Margin="20,40,18,0" VerticalAlignment="Top" Name="PanLabyMod" />
<Grid x:Name="PanLabyModInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
Tag="True">
<Grid.RenderTransform>
<TranslateTransform />
</Grid.RenderTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Image x:Name="ImgLabyMod" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
RenderOptions.BitmapScalingMode="Linear"
Source="pack://application:,,,/images/Blocks/LabyMod.png" />
<TextBlock x:Name="LabLabyMod" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Grid.Column="1" />
</Grid>
<Grid x:Name="BtnLabyModClear" Height="30" Width="30" HorizontalAlignment="Right"
VerticalAlignment="Top" Margin="0,5,32,0"
Background="{StaticResource ColorBrushSemiTransparent}">
<Path x:Name="BtnLabyModClearInner" Height="10" Width="10" Stretch="Uniform"
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
</Grid>
</local:MyCard>
<local:MyCard Title="{DynamicResource Common.Installation.OptiFine}" Height="40" Margin="0,12,0,0" x:Name="CardOptiFine"
IsSwapped="True" CanSwap="True" SwapLogoRight="True">
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top">
<StackPanel Name="PanOptiFine" />
</StackPanel>
<Grid x:Name="PanOptiFineInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
Tag="True">
<Grid.RenderTransform>
<TranslateTransform />
</Grid.RenderTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Image x:Name="ImgOptiFine" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
RenderOptions.BitmapScalingMode="Linear"
Source="pack://application:,,,/images/Blocks/GrassPath.png" />
<TextBlock x:Name="LabOptiFine" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Grid.Column="1" />
</Grid>
<Grid x:Name="BtnOptiFineClear" Height="30" Width="30" HorizontalAlignment="Right"
VerticalAlignment="Top" Margin="0,5,32,0"
Background="{StaticResource ColorBrushSemiTransparent}">
<Path x:Name="BtnOptiFineClearInner" Height="10" Width="10" Stretch="Uniform"
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
</Grid>
</local:MyCard>
<local:MyCard Title="{DynamicResource Common.Installation.OptiFabric}" Height="40" Margin="0,12,0,0" x:Name="CardOptiFabric"
IsSwapped="True" CanSwap="True" SwapLogoRight="True">
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanOptiFabric" />
<Grid x:Name="PanOptiFabricInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
Tag="True">
<Grid.RenderTransform>
<TranslateTransform />
</Grid.RenderTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Image x:Name="ImgOptiFabric" Margin="0,0,7,0" SnapsToDevicePixels="True" Height="18"
RenderOptions.BitmapScalingMode="HighQuality"
Source="pack://application:,,,/images/Blocks/OptiFabric.png" />
<TextBlock x:Name="LabOptiFabric" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Grid.Column="1" />
</Grid>
<Grid x:Name="BtnOptiFabricClear" Height="30" Width="30" HorizontalAlignment="Right"
VerticalAlignment="Top" Margin="0,5,32,0"
Background="{StaticResource ColorBrushSemiTransparent}">
<Path x:Name="BtnOptiFabricClearInner" Height="10" Width="10" Stretch="Uniform"
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
</Grid>
</local:MyCard>
<local:MyCard Title="{DynamicResource Common.Installation.LiteLoader}" Height="40" Margin="0,12,0,0" x:Name="CardLiteLoader"
IsSwapped="True" CanSwap="True" SwapLogoRight="True">
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanLiteLoader" />
<Grid x:Name="PanLiteLoaderInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top"
Tag="True">
<Grid.RenderTransform>
<TranslateTransform />
</Grid.RenderTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Image x:Name="ImgLiteLoader" Margin="-1,0,6,0" SnapsToDevicePixels="True" Height="20"
RenderOptions.BitmapScalingMode="Linear"
Source="pack://application:,,,/images/Blocks/Egg.png" />
<TextBlock x:Name="LabLiteLoader" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" Grid.Column="1" />
</Grid>
<Grid x:Name="BtnLiteLoaderClear" Height="30" Width="30" HorizontalAlignment="Right"
VerticalAlignment="Top" Margin="0,5,32,0"
Background="{StaticResource ColorBrushSemiTransparent}">
<Path x:Name="BtnLiteLoaderClearInner" Height="10" Width="10" Stretch="Uniform"
Fill="{StaticResource ColorBrushGray1}" HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
</Grid>
</local:MyCard>
</StackPanel>
</Grid>
</local:MyScrollViewer>
<local:MyExtraTextButton x:Name="BtnStart" Text="{DynamicResource Download.Install.StartDownload}"
HorizontalAlignment="Center" VerticalAlignment="Bottom"
LogoScale="0.95"
SvgIcon="lucide/download" />
</Grid>
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40,0" SnapsToDevicePixels="True"
x:Name="PanLoad" UseAnimation="False">
<local:MyLoading Margin="20,20,20,17" x:Name="LoadMinecraft" HorizontalAlignment="Center"
VerticalAlignment="Center" />
<local:MyLoading Visibility="Collapsed" x:Name="LoadOptiFine" HasAnimation="False" />
<local:MyLoading Visibility="Collapsed" x:Name="LoadForge" HasAnimation="False" />
<local:MyLoading Visibility="Collapsed" x:Name="LoadNeoForge" HasAnimation="False" />
<local:MyLoading Visibility="Collapsed" x:Name="LoadCleanroom" HasAnimation="False" />
<local:MyLoading Visibility="Collapsed" x:Name="LoadLiteLoader" HasAnimation="False" />
<local:MyLoading Visibility="Collapsed" x:Name="LoadFabric" HasAnimation="False" />
<local:MyLoading Visibility="Collapsed" x:Name="LoadLegacyFabric" HasAnimation="False" />
<local:MyLoading Visibility="Collapsed" x:Name="LoadFabricApi" HasAnimation="False" />
<local:MyLoading Visibility="Collapsed" x:Name="LoadLegacyFabricApi" HasAnimation="False" />
<local:MyLoading Visibility="Collapsed" x:Name="LoadLabyMod" HasAnimation="False" />
<local:MyLoading Visibility="Collapsed" x:Name="LoadOptiFabric" HasAnimation="False" />
</local:MyCard>
</Grid>
</local:MyPageRight>
<!--
指不定能把这俩玩意儿提成新控件:
<Grid Height="40">
<local:SystemDropShadowChrome Margin="-9.5,-9,0.5,-0.5" Opacity="0.1" CornerRadius="20" Color="{DynamicResource ColorObject1}" />
<corelocal:BlurBorder BorderThickness="0.001" Background="{DynamicResource ColorBrush3}" CornerRadius="20">
<StackPanel Orientation="Horizontal" Margin="13,0" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False">
<Path Stretch="Uniform" Height="13" VerticalAlignment="Center" Fill="{DynamicResource ColorBrush8}" RenderTransformOrigin="0.5,0.5"
Data="M1097 584 250 584 562 896C591 925 591 972 562 1001 533 1030 487 1030 458 1001L21 565C6 550-0 531 0 511L0 511 0 511C-0 492 6 472 21 457L458 21C487-7 533-7 562 21 591 50 591 97 562 126L250 438 1097 438C1137 438 1170 471 1170 511 1170 551 1137 584 1097 584L1097 584Z" />
</StackPanel>
</corelocal:BlurBorder>
</Grid>
<Grid Height="40" Grid.Column="2">
<local:SystemDropShadowChrome Margin="-9.5,-9,0.5,-0.5" Opacity="0.1" CornerRadius="20" Color="{DynamicResource ColorObject1}" />
<corelocal:BlurBorder BorderThickness="0.001" Background="{DynamicResource ColorBrush3}" CornerRadius="20">
<StackPanel Orientation="Horizontal" Margin="20,0" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False">
<Path Stretch="Uniform" Height="13" VerticalAlignment="Center" Fill="{DynamicResource ColorBrush8}" RenderTransformOrigin="0.5,0.5"
Data="M73 584L920 584 608 896C579 925 579 972 608 1001 637 1030 683 1030 712 1001L1149 565C1164 550 1170 531 1170 511 1170 492 1164 472 1149 457L712 21C683-7 637-7 608 21 579 50 579 97 608 126L920 438 73 438C33 438 0 471 0 511 0 551 33 584 73 584Z" />
<TextBlock VerticalAlignment="Center" Foreground="{DynamicResource ColorBrush8}" FontSize="15" Margin="10,0,-2,1" Text="{DynamicResource Download.Install.StartDownload}" />
</StackPanel>
</corelocal:BlurBorder>
</Grid>
-->
@@ -0,0 +1,2327 @@
using System.Collections;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using PCL.Core.App;
using PCL.Core.Utils.Validate;
using PCL.Core.App.Localization;
namespace PCL;
public partial class PageDownloadInstall
{
private bool isLoad;
public PageDownloadInstall()
{
Initialized += (_, _) => LoaderInit();
Loaded += (_, _) => Init();
InitializeComponent();
PanScroll = PanBack;
LoadMinecraft.Text = Lang.Text("Download.Version.LoadingList");
WeakLanguageChanged.Add(this, OnLanguageChanged);
BtnBack.Click += (_, _) => ExitSelectPage();
CardOptiFine.Swap += (_, _) => ReloadSelected();
LoadOptiFine.StateChanged += (_, _, _) => ReloadSelected();
CardForge.Swap += (_, _) => ReloadSelected();
LoadForge.StateChanged += (_, _, _) => ReloadSelected();
CardNeoForge.Swap += (_, _) => ReloadSelected();
LoadNeoForge.StateChanged += (_, _, _) => ReloadSelected();
CardFabric.Swap += (_, _) => ReloadSelected();
LoadFabric.StateChanged += (_, _, _) => ReloadSelected();
CardFabricApi.Swap += (_, _) => ReloadSelected();
LoadFabricApi.StateChanged += (_, _, _) => ReloadSelected();
CardOptiFabric.Swap += (_, _) => ReloadSelected();
LoadOptiFabric.StateChanged += (_, _, _) => ReloadSelected();
CardLiteLoader.Swap += (_, _) => ReloadSelected();
LoadLiteLoader.StateChanged += (_, _, _) => ReloadSelected();
LoadCleanroom.StateChanged += (_, _, _) => ReloadSelected();
CardCleanroom.Swap += (_, _) => ReloadSelected();
LoadLabyMod.StateChanged += (_, _, _) => ReloadSelected();
CardLabyMod.Swap += (_, _) => ReloadSelected();
TextSelectName.TextChanged += TextSelectName_TextChanged;
TextSelectName.ValidateChanged += TextSelectName_ValidateChanged;
CardOptiFine.PreviewSwap += CardOptiFine_PreviewSwap;
LoadOptiFine.StateChanged += (_, _, _) => OptiFine_Loaded();
BtnOptiFineClear.MouseLeftButtonUp += OptiFine_Clear;
CardLiteLoader.PreviewSwap += CardLiteLoader_PreviewSwap;
LoadLiteLoader.StateChanged += (_, _, _) => LiteLoader_Loaded();
BtnLiteLoaderClear.MouseLeftButtonUp += LiteLoader_Clear;
CardForge.PreviewSwap += CardForge_PreviewSwap;
LoadForge.StateChanged += (_, _, _) => Forge_Loaded();
BtnForgeClear.MouseLeftButtonUp += Forge_Clear;
CardNeoForge.PreviewSwap += CardNeoForge_PreviewSwap;
LoadNeoForge.StateChanged += (_, _, _) => NeoForge_Loaded();
BtnNeoForgeClear.MouseLeftButtonUp += NeoForge_Clear;
CardCleanroom.PreviewSwap += CardCleanroom_PreviewSwap;
LoadCleanroom.StateChanged += (_, _, _) => Cleanroom_Loaded();
BtnCleanroomClear.MouseLeftButtonUp += Cleanroom_Clear;
CardFabric.PreviewSwap += CardFabric_PreviewSwap;
LoadFabric.StateChanged += (_, _, _) => Fabric_Loaded();
BtnFabricClear.MouseLeftButtonUp += Fabric_Clear;
CardFabricApi.PreviewSwap += CardFabricApi_PreviewSwap;
LoadFabricApi.StateChanged += (_, _, _) => FabricApi_Loaded();
BtnFabricApiClear.MouseLeftButtonUp += FabricApi_Clear;
CardLegacyFabric.PreviewSwap += CardLegacyFabric_PreviewSwap;
LoadLegacyFabric.StateChanged += (_, _, _) => LegacyFabric_Loaded();
BtnLegacyFabricClear.MouseLeftButtonUp += LegacyFabric_Clear;
CardLegacyFabricApi.PreviewSwap += CardLegacyFabricApi_PreviewSwap;
LoadLegacyFabricApi.StateChanged += (_, _, _) => LegacyFabricApi_Loaded();
BtnLegacyFabricApiClear.MouseLeftButtonUp += LegacyFabricApi_Clear;
CardOptiFabric.PreviewSwap += CardOptiFabric_PreviewSwap;
LoadOptiFabric.StateChanged += (_, _, _) => OptiFabric_Loaded();
BtnOptiFabricClear.MouseLeftButtonUp += OptiFabric_Clear;
CardLabyMod.PreviewSwap += CardLabyMod_PreviewSwap;
LoadLabyMod.StateChanged += (_, _, _) => LabyMod_Loaded();
BtnLabyModClear.MouseLeftButtonUp += LabyMod_Clear;
TextSelectName.KeyDown += TextSelectName_KeyDown;
BtnStart.Click += (_, _) => BtnStart_Click();
}
private static void OnLanguageChanged(PageDownloadInstall page) => page._OnLanguageChanged();
private void LoaderInit()
{
disabledPageAnimControls.Add(BtnStart);
PageLoaderInit(LoadMinecraft, PanLoad, PanAllBack, null, ModDownload.dlClientListLoader,
_ => LoadMinecraft_OnFinish());
}
private void Init()
{
PanBack.ScrollToHome();
ModDownload.dlOptiFineListLoader.Start();
ModDownload.dlLiteLoaderListLoader.Start();
ModDownload.dlFabricListLoader.Start();
ModDownload.dlNeoForgeListLoader.Start();
ModDownload.dlCleanroomListLoader.Start();
ModDownload.dlLabyModListLoader.Start();
ModDownload.dlLegacyFabricListLoader.Start();
// 重载预览
TextSelectName.ValidateRules = [new FolderNameValidator(ModFolder.mcFolderSelected + "versions")];
TextSelectName.Validate();
ReloadSelected();
// 非重复加载部分
if (isLoad)
return;
isLoad = true;
ModDownloadLib.McDownloadForgeRecommendedRefresh();
LoadOptiFine.State = ModDownload.dlOptiFineListLoader;
LoadLiteLoader.State = ModDownload.dlLiteLoaderListLoader;
LoadFabric.State = ModDownload.dlFabricListLoader;
LoadFabricApi.State = ModDownload.dlFabricApiLoader;
LoadNeoForge.State = ModDownload.dlNeoForgeListLoader;
LoadCleanroom.State = ModDownload.dlCleanroomListLoader;
LoadOptiFabric.State = ModDownload.dlOptiFabricLoader;
LoadLabyMod.State = ModDownload.dlLabyModListLoader;
LoadLegacyFabric.State = ModDownload.dlLegacyFabricListLoader;
LoadLegacyFabricApi.State = ModDownload.dlLegacyFabricApiLoader;
}
private string GetLoaderError(MyLoading loader)
{
if (loader is null || !loader.State.IsLoader)
return Lang.Text("Download.Install.State.Getting");
switch (loader.State.LoadingState)
{
case MyLoading.MyLoadingState.Run:
{
return Lang.Text("Download.Install.State.Getting");
}
case MyLoading.MyLoadingState.Error:
{
var message = ((ModLoader.LoaderBase)loader.State).Error.Message;
return message == Lang.Text("Download.Install.State.NoVersion")
? Lang.Text("Download.Install.State.NoVersion")
: Lang.Text("Download.Install.State.GetFailed", message);
}
case MyLoading.MyLoadingState.Unloaded:
{
return Lang.Text("Download.Install.State.UnknownUnloaded");
}
default:
{
return null;
}
}
}
private void BtnBack_Click(object sender, EventArgs e)
{
ExitSelectPage();
}
#region
// 页面切换动画
public bool isInSelectPage;
private bool isFirstLoaded;
private void EnterSelectPage()
{
if (isInSelectPage)
return;
isInSelectPage = true;
PanInner.Margin = new Thickness(25d, 10d, 25d, 40d);
autoSelectedFabricApi = false;
autoSelectedOptiFabric = false;
isSelectNameEdited = false;
PanSelect.Visibility = Visibility.Visible;
PanSelect.IsHitTestVisible = true;
PanMinecraft.IsHitTestVisible = false;
PanBack.IsHitTestVisible = false;
PanBack.ScrollToHome();
disabledPageAnimControls.Remove(BtnStart);
BtnStart.Show = true;
CardOptiFine.IsSwapped = true;
CardLiteLoader.IsSwapped = true;
CardForge.IsSwapped = true;
CardNeoForge.IsSwapped = true;
CardCleanroom.IsSwapped = true;
CardFabric.IsSwapped = true;
CardFabricApi.IsSwapped = true;
CardOptiFabric.IsSwapped = true;
CardLabyMod.IsSwapped = true;
if (!States.Hint.InstallPageBack)
{
States.Hint.InstallPageBack = true;
HintService.Hint(Lang.Text("Download.Install.Hint.MinecraftBack"));
}
// 如果在选择页面按了刷新键,选择页的东西可能会由于动画被隐藏,但不会由于加载结束而再次显示,因此这里需要手动恢复
foreach (var control in GetAllAnimControls(PanSelect))
{
control.Opacity = 1d;
if (control.RenderTransform is null || control.RenderTransform is TranslateTransform)
control.RenderTransform = new TranslateTransform();
}
// 启动 Forge 加载
if (McInstanceInfo.IsFormatFit(_vanillaName))
{
var forgeLoader =
new ModLoader.LoaderTask<string, List<ModDownload.DlForgeVersionEntry>>(
"DlForgeVersion " + _vanillaName, ModDownload.DlForgeVersionMain);
LoadForge.State = forgeLoader;
forgeLoader.Start(_vanillaName);
}
// 启动 Fabric API、Legacy Fabric API、OptiFabric、LabyMod 加载
ModDownload.dlFabricApiLoader.Start();
ModDownload.dlLegacyFabricApiLoader.Start();
ModDownload.dlOptiFabricLoader.Start();
ModDownload.dlLabyModListLoader.Start();
ModAnimation.AniStart(new[]
{
ModAnimation.AaOpacity(PanMinecraft, -PanMinecraft.Opacity, 70, 10),
ModAnimation.AaTranslateX(PanMinecraft, -50 - ((TranslateTransform)PanMinecraft.RenderTransform).X, 90, 10),
ModAnimation.AaCode(() =>
{
PanBack.ScrollToHome();
TextSelectName.Validate();
OptiFine_Loaded();
LiteLoader_Loaded();
Forge_Loaded();
NeoForge_Loaded();
Cleanroom_Loaded();
Fabric_Loaded();
LegacyFabric_Loaded();
FabricApi_Loaded();
LegacyFabricApi_Loaded();
OptiFabric_Loaded();
LabyMod_Loaded();
ReloadSelected();
PanMinecraft.Visibility = Visibility.Collapsed;
}, after: true),
ModAnimation.AaOpacity(PanSelect, 1d - PanSelect.Opacity, 70, 100),
ModAnimation.AaTranslateX(PanSelect, -((TranslateTransform)PanSelect.RenderTransform).X, 160, 100,
new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.ExtraStrong)),
ModAnimation.AaCode(() =>
{
PanBack.IsHitTestVisible = true;
// 初始化 Binding
if (isFirstLoaded)
return;
isFirstLoaded = true;
BtnOptiFineClearInner.SetBinding(Shape.FillProperty,
new Binding("Foreground") { Source = CardOptiFine.MainTextBlock, Mode = BindingMode.OneWay });
BtnLiteLoaderClearInner.SetBinding(Shape.FillProperty,
new Binding("Foreground") { Source = CardLiteLoader.MainTextBlock, Mode = BindingMode.OneWay });
BtnForgeClearInner.SetBinding(Shape.FillProperty,
new Binding("Foreground") { Source = CardForge.MainTextBlock, Mode = BindingMode.OneWay });
BtnNeoForgeClearInner.SetBinding(Shape.FillProperty,
new Binding("Foreground") { Source = CardNeoForge.MainTextBlock, Mode = BindingMode.OneWay });
BtnCleanroomClearInner.SetBinding(Shape.FillProperty,
new Binding("Foreground") { Source = CardCleanroom.MainTextBlock, Mode = BindingMode.OneWay });
BtnFabricClearInner.SetBinding(Shape.FillProperty,
new Binding("Foreground") { Source = CardFabric.MainTextBlock, Mode = BindingMode.OneWay });
BtnLegacyFabricClearInner.SetBinding(Shape.FillProperty,
new Binding("Foreground") { Source = CardLegacyFabric.MainTextBlock, Mode = BindingMode.OneWay });
BtnFabricApiClearInner.SetBinding(Shape.FillProperty,
new Binding("Foreground") { Source = CardFabricApi.MainTextBlock, Mode = BindingMode.OneWay });
BtnLegacyFabricApiClearInner.SetBinding(Shape.FillProperty,
new Binding("Foreground")
{ Source = CardLegacyFabricApi.MainTextBlock, Mode = BindingMode.OneWay });
BtnLabyModClearInner.SetBinding(Shape.FillProperty,
new Binding("Foreground") { Source = CardLabyMod.MainTextBlock, Mode = BindingMode.OneWay });
BtnOptiFabricClearInner.SetBinding(Shape.FillProperty,
new Binding("Foreground") { Source = CardOptiFabric.MainTextBlock, Mode = BindingMode.OneWay });
}, after: true)
}, "FrmDownloadInstall SelectPageSwitch", true);
}
public void ExitSelectPage()
{
if (!isInSelectPage)
return;
isInSelectPage = false;
PanInner.Margin = new Thickness(25d, 10d, 25d, 25d);
disabledPageAnimControls.Add(BtnStart);
BtnStart.Show = false;
ClearSelected(); // 清除已选择项
PanMinecraft.Visibility = Visibility.Visible;
PanSelect.IsHitTestVisible = false;
PanMinecraft.IsHitTestVisible = true;
PanBack.IsHitTestVisible = false;
PanBack.ScrollToHome();
ModAnimation.AniStart(new[]
{
ModAnimation.AaOpacity(PanSelect, -PanSelect.Opacity, 70, 10),
ModAnimation.AaTranslateX(PanSelect, 50d - ((TranslateTransform)PanSelect.RenderTransform).X, 90, 10),
ModAnimation.AaCode(() => PanBack.ScrollToHome(), after: true),
ModAnimation.AaOpacity(PanMinecraft, 1d - PanMinecraft.Opacity, 70, 100),
ModAnimation.AaTranslateX(PanMinecraft, -((TranslateTransform)PanMinecraft.RenderTransform).X, 160, 100,
new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.ExtraStrong)),
ModAnimation.AaCode(() =>
{
PanSelect.Visibility = Visibility.Collapsed;
PanBack.IsHitTestVisible = true;
}, after: true)
}, "FrmDownloadInstall SelectPageSwitch");
}
public void MinecraftSelected(MyListItem sender, MouseButtonEventArgs e)
{
_vanillaName = sender.Title;
_vanillaData = (JsonObject)(dynamic)sender.Tag;
_vanillaIcon = sender.Logo;
EnterSelectPage();
}
#endregion
#region
// Minecraft
private string? _vanillaName;
private JsonObject? _vanillaData;
private string? _vanillaIcon;
private int VanillaDrop => McInstanceInfo.VersionToDrop(_vanillaName, true);
// OptiFine
private ModDownload.DlOptiFineListEntry? selectedOptiFine;
/// <summary>
/// 选定的 Mod Loader 名称,内容应为 Forge / NeoForge / Fabric / Cleanroom / LabyMod / LegacyFabric
/// </summary>
private string? selectedLoaderName;
/// <summary>
/// 选定的 Mod Loader API 名称,内容应为 Fabric API
/// </summary>
private string? selectedAPIName;
// LiteLoader
private ModDownload.DlLiteLoaderListEntry? selectedLiteLoader;
// Forge
private ModDownload.DlForgeVersionEntry? selectedForge;
// Cleanroom
private ModDownload.DlCleanroomListEntry? selectedCleanroom;
// NeoForge
private ModDownload.DlNeoForgeListEntry? selectedNeoForge;
// Fabric
private string? selectedFabric;
// FabricApi
private ModComp.CompFile? selectedFabricApi;
// LegacyFabric
private string? selectedLegacyFabric;
// Legacy FabricApi
private ModComp.CompFile? selectedLegacyFabricApi;
// LabyMod
private string? selectedLabyModChannel;
private string? selectedLabyModCommitRef;
private string? selectedLabyModVersion;
private string? selectedLabyModBaseVersion;
// OptiFabric
private ModComp.CompFile? selectedOptiFabric;
private bool _ReloadSelected_Ongoing; // #3742 中,LoadOptiFineGetError 会初始化 LoadOptiFine,触发事件 LoadOptiFine.StateChanged,导致再次调用 SelectReload
/// <summary>
/// 重载已选择的项目的显示。
/// </summary>
private void ReloadSelected()
{
if (_vanillaName is null || _ReloadSelected_Ongoing)
return;
_ReloadSelected_Ongoing = true;
// 主预览
try
{
SelectNameUpdate();
ImgLogo.Source = GetSelectLogo();
// OptiFine
if (!McVersionComparer.CompareVersionGe(_vanillaName, "1.7.2"))
{
CardOptiFine.Visibility = Visibility.Collapsed;
}
else
{
CardOptiFine.Visibility = Visibility.Visible;
var optiFineError = LoadOptiFineGetError();
CardOptiFine.MainSwap.Visibility = optiFineError is null ? Visibility.Visible : Visibility.Collapsed;
if (optiFineError is not null)
CardOptiFine.IsSwapped = true;
SetPanelVisibility(PanOptiFineInfo, CardOptiFine.IsSwapped);
if (selectedOptiFine is null)
{
BtnOptiFineClear.Visibility = Visibility.Collapsed;
ImgOptiFine.Visibility = Visibility.Collapsed;
LabOptiFine.Text = optiFineError ?? Lang.Text("Download.Install.State.CanAdd");
LabOptiFine.Foreground = ThemeManager.colorGray4;
}
else
{
BtnOptiFineClear.Visibility = Visibility.Visible;
ImgOptiFine.Visibility = Visibility.Visible;
LabOptiFine.Text = selectedOptiFine.DisplayName.Replace(_vanillaName + " ", "");
LabOptiFine.Foreground = ThemeManager.colorGray1;
}
}
// LiteLoader
if (!McInstanceInfo.IsFormatFit(_vanillaName)
|| !McVersionComparer.CompareVersionGe(_vanillaName, "1.5.2")
|| !McVersionComparer.CompareVersionGe("1.12.2", _vanillaName))
{
CardLiteLoader.Visibility = Visibility.Collapsed;
}
else
{
CardLiteLoader.Visibility = Visibility.Visible;
var liteLoaderError = LoadLiteLoaderGetError();
CardLiteLoader.MainSwap.Visibility = liteLoaderError is null ? Visibility.Visible : Visibility.Collapsed;
if (liteLoaderError is not null)
CardLiteLoader.IsSwapped = true; // 例如在同时展开卡片时选择了不兼容项则强制折叠
SetPanelVisibility(PanLiteLoaderInfo, CardLiteLoader.IsSwapped);
if (selectedLiteLoader is null)
{
BtnLiteLoaderClear.Visibility = Visibility.Collapsed;
ImgLiteLoader.Visibility = Visibility.Collapsed;
LabLiteLoader.Text = liteLoaderError ?? Lang.Text("Download.Install.State.CanAdd");
LabLiteLoader.Foreground = ThemeManager.colorGray4;
}
else
{
BtnLiteLoaderClear.Visibility = Visibility.Visible;
ImgLiteLoader.Visibility = Visibility.Visible;
LabLiteLoader.Text = selectedLiteLoader.Inherit;
LabLiteLoader.Foreground = ThemeManager.colorGray1;
}
}
// Forge
if (!McInstanceInfo.IsFormatFit(_vanillaName)
|| !McVersionComparer.CompareVersionGe(_vanillaName, "1.1"))
{
CardForge.Visibility = Visibility.Collapsed;
}
else
{
CardForge.Visibility = Visibility.Visible;
var forgeError = LoadForgeGetError();
CardForge.MainSwap.Visibility = forgeError is null ? Visibility.Visible : Visibility.Collapsed;
if (forgeError is not null)
CardForge.IsSwapped = true;
SetPanelVisibility(PanForgeInfo, CardForge.IsSwapped);
if (selectedForge is null)
{
BtnForgeClear.Visibility = Visibility.Collapsed;
ImgForge.Visibility = Visibility.Collapsed;
LabForge.Text = forgeError ?? Lang.Text("Download.Install.State.CanAdd");
LabForge.Foreground = ThemeManager.colorGray4;
}
else
{
BtnForgeClear.Visibility = Visibility.Visible;
ImgForge.Visibility = Visibility.Visible;
LabForge.Text = selectedForge.VersionName;
LabForge.Foreground = ThemeManager.colorGray1;
}
}
// Cleanroom
if (_vanillaName == "1.12.2")
{
CardCleanroom.Visibility = Visibility.Visible;
var cleanroomError = LoadCleanroomGetError();
CardCleanroom.MainSwap.Visibility = cleanroomError is null ? Visibility.Visible : Visibility.Collapsed;
if (cleanroomError is not null)
CardCleanroom.IsSwapped = true;
SetPanelVisibility(PanCleanroomInfo, CardCleanroom.IsSwapped);
if (selectedCleanroom is null)
{
BtnCleanroomClear.Visibility = Visibility.Collapsed;
ImgCleanroom.Visibility = Visibility.Collapsed;
LabCleanroom.Text = cleanroomError ?? Lang.Text("Download.Install.State.CanAdd");
LabCleanroom.Foreground = ThemeManager.colorGray4;
}
else
{
BtnCleanroomClear.Visibility = Visibility.Visible;
ImgCleanroom.Visibility = Visibility.Visible;
LabCleanroom.Text = selectedCleanroom.VersionName;
LabCleanroom.Foreground = ThemeManager.colorGray1;
}
}
else
{
CardCleanroom.Visibility = Visibility.Collapsed;
}
// NeoForge
if (!McVersionComparer.CompareVersionGe(_vanillaName, "1.20.1"))
{
CardNeoForge.Visibility = Visibility.Collapsed;
}
else
{
CardNeoForge.Visibility = Visibility.Visible;
var neoForgeError = LoadNeoForgeGetError();
CardNeoForge.MainSwap.Visibility = neoForgeError is null ? Visibility.Visible : Visibility.Collapsed;
if (neoForgeError is not null)
CardNeoForge.IsSwapped = true;
SetPanelVisibility(PanNeoForgeInfo, CardNeoForge.IsSwapped);
if (selectedNeoForge is null)
{
BtnNeoForgeClear.Visibility = Visibility.Collapsed;
ImgNeoForge.Visibility = Visibility.Collapsed;
LabNeoForge.Text = neoForgeError ?? Lang.Text("Download.Install.State.CanAdd");
LabNeoForge.Foreground = ThemeManager.colorGray4;
}
else
{
BtnNeoForgeClear.Visibility = Visibility.Visible;
ImgNeoForge.Visibility = Visibility.Visible;
LabNeoForge.Text = selectedNeoForge.VersionName;
LabNeoForge.Foreground = ThemeManager.colorGray1;
}
}
// Fabric
if (VanillaDrop < 130
|| (VanillaDrop == 130 && !McVersionComparer.CompareVersionGe(_vanillaName, "18w43b")))
{
CardFabric.Visibility = Visibility.Collapsed;
}
else
{
CardFabric.Visibility = Visibility.Visible;
var fabricError = LoadFabricGetError();
CardFabric.MainSwap.Visibility = fabricError is null ? Visibility.Visible : Visibility.Collapsed;
if (fabricError is not null)
CardFabric.IsSwapped = true;
SetPanelVisibility(PanFabricInfo, CardFabric.IsSwapped);
if (selectedFabric is null)
{
BtnFabricClear.Visibility = Visibility.Collapsed;
ImgFabric.Visibility = Visibility.Collapsed;
LabFabric.Text = fabricError ?? Lang.Text("Download.Install.State.CanAdd");
LabFabric.Foreground = ThemeManager.colorGray4;
}
else
{
BtnFabricClear.Visibility = Visibility.Visible;
ImgFabric.Visibility = Visibility.Visible;
LabFabric.Text = selectedFabric.Replace("+build", "");
LabFabric.Foreground = ThemeManager.colorGray1;
}
}
// FabricApi
if (selectedFabric is null)
{
CardFabricApi.Visibility = Visibility.Collapsed;
}
else
{
CardFabricApi.Visibility = Visibility.Visible;
var fabricApiError = LoadFabricApiGetError();
CardFabricApi.MainSwap.Visibility = fabricApiError is null ? Visibility.Visible : Visibility.Collapsed;
if (fabricApiError is not null || selectedFabric is null)
CardFabricApi.IsSwapped = true;
SetPanelVisibility(PanFabricApiInfo, CardFabricApi.IsSwapped);
if (selectedFabricApi is null)
{
BtnFabricApiClear.Visibility = Visibility.Collapsed;
ImgFabricApi.Visibility = Visibility.Collapsed;
LabFabricApi.Text = fabricApiError ?? Lang.Text("Download.Install.State.CanAdd");
LabFabricApi.Foreground = ThemeManager.colorGray4;
}
else
{
BtnFabricApiClear.Visibility = Visibility.Visible;
ImgFabricApi.Visibility = Visibility.Visible;
LabFabricApi.Text = selectedFabricApi.DisplayName.Split("]")[1].Replace("Fabric API ", "")
.Replace(" build ", ".").Trim();
LabFabricApi.Foreground = ThemeManager.colorGray1;
}
}
// LegacyFabric
if (VanillaDrop < 30 || VanillaDrop > 130)
{
CardLegacyFabric.Visibility = Visibility.Collapsed;
}
else
{
CardLegacyFabric.Visibility = Visibility.Visible;
var legacyFabricError = LoadLegacyFabricGetError();
CardLegacyFabric.MainSwap.Visibility =
legacyFabricError is null ? Visibility.Visible : Visibility.Collapsed;
if (legacyFabricError is not null)
CardLegacyFabric.IsSwapped = true;
SetPanelVisibility(PanLegacyFabricInfo, CardLegacyFabric.IsSwapped);
if (selectedLegacyFabric is null)
{
BtnLegacyFabricClear.Visibility = Visibility.Collapsed;
ImgLegacyFabric.Visibility = Visibility.Collapsed;
LabLegacyFabric.Text = legacyFabricError ?? Lang.Text("Download.Install.State.CanAdd");
LabLegacyFabric.Foreground = ThemeManager.colorGray4;
}
else
{
BtnLegacyFabricClear.Visibility = Visibility.Visible;
ImgLegacyFabric.Visibility = Visibility.Visible;
LabLegacyFabric.Text = selectedLegacyFabric.Replace("+build", "");
LabLegacyFabric.Foreground = ThemeManager.colorGray1;
}
}
// LegacyFabricApi
if (selectedLegacyFabric is null)
{
CardLegacyFabricApi.Visibility = Visibility.Collapsed;
}
else
{
CardLegacyFabricApi.Visibility = Visibility.Visible;
var legacyFabricApiError = LoadLegacyFabricApiGetError();
CardLegacyFabricApi.MainSwap.Visibility =
legacyFabricApiError is null ? Visibility.Visible : Visibility.Collapsed;
if (legacyFabricApiError is not null || selectedLegacyFabric is null)
CardLegacyFabricApi.IsSwapped = true;
SetPanelVisibility(PanLegacyFabricApiInfo, CardLegacyFabricApi.IsSwapped);
if (selectedLegacyFabricApi is null)
{
BtnLegacyFabricApiClear.Visibility = Visibility.Collapsed;
ImgLegacyFabricApi.Visibility = Visibility.Collapsed;
LabLegacyFabricApi.Text = legacyFabricApiError ?? Lang.Text("Download.Install.State.CanAdd");
LabLegacyFabricApi.Foreground = ThemeManager.colorGray4;
}
else
{
BtnLegacyFabricApiClear.Visibility = Visibility.Visible;
ImgLegacyFabricApi.Visibility = Visibility.Visible;
LabLegacyFabricApi.Text = selectedLegacyFabricApi.DisplayName.Replace("Legacy Fabric API ", "");
LabLegacyFabricApi.Foreground = ThemeManager.colorGray1;
}
}
// LabyMod
if (!McInstanceInfo.IsFormatFit(_vanillaName)
|| !McVersionComparer.CompareVersionGe(_vanillaName, "1.8.9"))
{
CardLabyMod.Visibility = Visibility.Collapsed;
}
else
{
CardLabyMod.Visibility = Visibility.Visible;
var labyModError = LoadLabyModGetError();
CardLabyMod.MainSwap.Visibility = labyModError is null ? Visibility.Visible : Visibility.Collapsed;
if (labyModError is not null)
CardLabyMod.IsSwapped = true;
SetPanelVisibility(PanLabyModInfo, CardLabyMod.IsSwapped);
if (selectedLabyModVersion is null)
{
BtnLabyModClear.Visibility = Visibility.Collapsed;
ImgLabyMod.Visibility = Visibility.Collapsed;
LabLabyMod.Text = labyModError ?? Lang.Text("Download.Install.State.CanAdd");
LabLabyMod.Foreground = ThemeManager.colorGray4;
}
else
{
BtnLabyModClear.Visibility = Visibility.Visible;
ImgLabyMod.Visibility = Visibility.Visible;
LabLabyMod.Text = selectedLabyModVersion;
LabLabyMod.Foreground = ThemeManager.colorGray1;
}
}
// OptiFabric
if (selectedFabric is null || selectedOptiFine is null)
{
CardOptiFabric.Visibility = Visibility.Collapsed;
}
else
{
CardOptiFabric.Visibility = Visibility.Visible;
var optiFabricError = LoadOptiFabricGetError();
CardOptiFabric.MainSwap.Visibility = optiFabricError is null ? Visibility.Visible : Visibility.Collapsed;
if (optiFabricError is not null || selectedFabric is null)
CardOptiFabric.IsSwapped = true;
SetPanelVisibility(PanOptiFabricInfo, CardOptiFabric.IsSwapped);
if (selectedOptiFabric is null)
{
BtnOptiFabricClear.Visibility = Visibility.Collapsed;
ImgOptiFabric.Visibility = Visibility.Collapsed;
LabOptiFabric.Text = optiFabricError ?? Lang.Text("Download.Install.State.CanAdd");
LabOptiFabric.Foreground = ThemeManager.colorGray4;
}
else
{
BtnOptiFabricClear.Visibility = Visibility.Visible;
ImgOptiFabric.Visibility = Visibility.Visible;
LabOptiFabric.Text = selectedOptiFabric.DisplayName.ToLower().Replace("optifabric-", "")
.Replace(".jar", "").Trim().TrimStart('v');
LabOptiFabric.Foreground = ThemeManager.colorGray1;
}
}
// 主警告
if (selectedFabric is not null && selectedFabricApi is null)
HintFabricAPI.Visibility = Visibility.Visible;
else
HintFabricAPI.Visibility = Visibility.Collapsed;
if (selectedLegacyFabric is not null && selectedLegacyFabricApi is null)
HintLegacyFabricAPI.Visibility = Visibility.Visible;
else
HintLegacyFabricAPI.Visibility = Visibility.Collapsed;
if ((selectedFabric is not null || selectedLegacyFabric is not null) && selectedOptiFine is not null &&
selectedOptiFabric is null)
{
if (VanillaDrop >= 140 && VanillaDrop <= 150)
{
HintOptiFabric.Visibility = Visibility.Collapsed;
HintLegacyOptiFabric.Visibility = Visibility.Collapsed;
HintOptiFabricOld.Visibility = Visibility.Visible;
}
else if (selectedLegacyFabric is not null)
{
HintOptiFabric.Visibility = Visibility.Collapsed;
HintLegacyOptiFabric.Visibility = Visibility.Visible;
HintOptiFabricOld.Visibility = Visibility.Collapsed;
}
else
{
HintOptiFabric.Visibility = Visibility.Visible;
HintOptiFabricOld.Visibility = Visibility.Collapsed;
HintLegacyOptiFabric.Visibility = Visibility.Collapsed;
}
}
else
{
HintOptiFabric.Visibility = Visibility.Collapsed;
HintOptiFabricOld.Visibility = Visibility.Collapsed;
HintLegacyOptiFabric.Visibility = Visibility.Collapsed;
}
if (VanillaDrop >= 160 && selectedOptiFine is not null &&
(selectedForge is not null || selectedFabric is not null))
HintModOptiFine.Visibility = Visibility.Visible;
else
HintModOptiFine.Visibility = Visibility.Collapsed;
// 结束
}
finally
{
_ReloadSelected_Ongoing = false;
}
}
/// <summary>
/// 清空已选择的项目。
/// </summary>
private void ClearSelected()
{
_vanillaName = null;
_vanillaData = null;
_vanillaIcon = null;
selectedOptiFine = null;
selectedLiteLoader = null;
selectedLoaderName = null;
selectedAPIName = null;
selectedForge = null;
selectedNeoForge = null;
selectedCleanroom = null;
selectedFabric = null;
selectedFabricApi = null;
selectedOptiFabric = null;
selectedLabyModCommitRef = null;
selectedLabyModVersion = null;
selectedLabyModBaseVersion = null;
selectedLabyModChannel = null;
selectedLegacyFabric = null;
selectedLegacyFabricApi = null;
}
// 信息栏动画
private void SetPanelVisibility(Grid panel, bool visible)
{
if (Equals(panel.Tag, visible.ToString()))
return;
panel.Tag = visible.ToString();
if (visible)
ModAnimation.AniStart(
new[]
{
ModAnimation.AaTranslateY(panel, -((TranslateTransform)panel.RenderTransform).Y, 150,
ease: new ModAnimation.AniEaseOutFluent()),
ModAnimation.AaOpacity(panel, 1d - panel.Opacity, 60)
}, "PageDownloadInstall Visibility " + panel.Name);
else
ModAnimation.AniStart(
new[]
{
ModAnimation.AaTranslateY(panel, 6d - ((TranslateTransform)panel.RenderTransform).Y, 60),
ModAnimation.AaOpacity(panel, -panel.Opacity, 60)
}, "PageDownloadInstall Visibility " + panel.Name);
}
/// <summary>
/// 获取实例图标。
/// </summary>
private string GetSelectLogo()
{
if (selectedFabric is not null) return "pack://application:,,,/images/Blocks/Fabric.png";
if (selectedLegacyFabric is not null) return "pack://application:,,,/images/Blocks/Fabric.png";
if (selectedForge is not null) return "pack://application:,,,/images/Blocks/Anvil.png";
if (selectedNeoForge is not null) return "pack://application:,,,/images/Blocks/NeoForge.png";
if (selectedLiteLoader is not null) return "pack://application:,,,/images/Blocks/Egg.png";
if (selectedOptiFine is not null) return "pack://application:,,,/images/Blocks/GrassPath.png";
if (selectedCleanroom is not null) return "pack://application:,,,/images/Blocks/Cleanroom.png";
if (selectedLabyModVersion is not null) return "pack://application:,,,/images/Blocks/LabyMod.png";
return _vanillaIcon;
}
// 实例名处理
/// <summary>
/// 获取默认实例名。
/// </summary>
private string GetSelectName()
{
var name = _vanillaName;
if (selectedFabric is not null) name += "-Fabric_" + selectedFabric.Replace("+build", "");
if (selectedLegacyFabric is not null) name += "-LegacyFabric_" + selectedLegacyFabric;
if (selectedLabyModVersion is not null)
name += "-LabyMod_" + selectedLabyModBaseVersion + (selectedLabyModChannel == "snapshot" ? "_Snapshot" : "_Production");
if (selectedForge is not null) name += "-Forge_" + selectedForge.VersionName;
if (selectedNeoForge is not null) name += "-NeoForge_" + selectedNeoForge.VersionName;
if (selectedCleanroom is not null) name += "-Cleanroom_" + selectedCleanroom.VersionName;
if (selectedLiteLoader is not null) name += "-LiteLoader";
if (selectedOptiFine is not null)
name += "-OptiFine_" + selectedOptiFine.DisplayName.Replace(_vanillaName + " ", "").Replace(" ", "_");
return name;
}
private bool isSelectNameEdited;
private bool isSelectNameChanging;
private void SelectNameUpdate()
{
if (isSelectNameEdited || isSelectNameChanging)
return;
isSelectNameChanging = true;
TextSelectName.Text = GetSelectName();
isSelectNameChanging = false;
}
private void TextSelectName_TextChanged(object sender, TextChangedEventArgs e)
{
if (isSelectNameChanging)
return;
isSelectNameEdited = true;
ReloadSelected();
}
private void TextSelectName_ValidateChanged(object sender, EventArgs e)
{
BtnStart.IsEnabled = TextSelectName.IsValidated;
}
#endregion
#region
// 结果数据化
private void LoadMinecraft_OnFinish()
{
ExitSelectPage(); // 返回
try
{
if (ModDownload.dlClientListLoader.output.Value["versions"] is not JsonArray versions)
return;
_RebuildVersionCards(versions);
if (mcVersionWaitingForSelect is null) return;
ModBase.Log("[Download] 自动选择 MC 版本:" + mcVersionWaitingForSelect);
foreach (JsonObject version1 in versions)
{
if (((string)version1["id"] ?? "") != mcVersionWaitingForSelect)
continue;
var item = ModDownloadLib.McDownloadListItem(version1, (_, _) => { }, false);
MinecraftSelected(item, null);
break;
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化安装版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Install.Error.OperationFailed"));
}
}
private void _RebuildVersionCards(JsonArray versions)
{
var categoryOrder = new[]
{
McVersionCategory.Release,
McVersionCategory.Snapshot,
McVersionCategory.BeforeRelease,
McVersionCategory.AprilFools
};
var dict = categoryOrder.ToDictionary(
category => category,
_ => new List<JsonObject>()
);
foreach (JsonObject version in versions)
{
var category = McVersionClassifier.ClassifyVersion(version);
dict[category].Add(version);
}
foreach (var category in categoryOrder)
dict[category] = dict[category]
.OrderByDescending(McVersionClassifier.GetReleaseTime)
.ToList();
PanMinecraft.Children.Clear();
_AddLatestVersionCard(dict);
_AddCategoryCards(dict, categoryOrder);
}
private void _OnLanguageChanged() => ModBase.RunInUi(() =>
{
LoadMinecraft.Text = Lang.Text("Download.Version.LoadingList");
if (ModDownload.dlClientListLoader.output.Value?["versions"] is JsonArray versions)
_RebuildVersionCards(versions);
});
private void _AddLatestVersionCard(Dictionary<McVersionCategory, List<JsonObject>> dict)
{
var latestRelease = dict[McVersionCategory.Release].FirstOrDefault();
var latestSnapshot = dict[McVersionCategory.Snapshot].FirstOrDefault();
var latestVersions = new List<JsonObject>();
if (latestRelease is not null)
{
var release = (JsonObject)latestRelease.DeepClone();
release["lore"] = Lang.Text(
"Download.Version.Latest.Release",
Lang.Date(McVersionClassifier.GetReleaseTime(release), "g")
);
latestVersions.Add(release);
}
if (latestSnapshot is not null &&
(latestRelease is null || McVersionClassifier.GetReleaseTime(latestRelease) <
McVersionClassifier.GetReleaseTime(latestSnapshot)))
{
var snapshot = (JsonObject)latestSnapshot.DeepClone();
snapshot["lore"] = Lang.Text(
"Download.Version.Latest.Development",
Lang.Date(McVersionClassifier.GetReleaseTime(snapshot), "g")
);
latestVersions.Add(snapshot);
}
if (latestVersions.Count == 0)
return;
var cardInfo = new MyCard
{
Title = Lang.Text("Download.Version.Latest.Title"),
Margin = new Thickness(0d, 15d, 0d, 15d)
};
var panInfo = _CreateVersionStack(latestVersions);
MyCard.StackInstall(ref panInfo, _StackInstall);
cardInfo.Children.Add(panInfo);
PanMinecraft.Children.Insert(0, cardInfo);
}
private void _AddCategoryCards(
Dictionary<McVersionCategory, List<JsonObject>> dict,
IEnumerable<McVersionCategory> categoryOrder)
{
foreach (var category in categoryOrder)
{
var versions = dict[category];
if (versions.Count == 0)
continue;
var card = new MyCard
{
Title = $"{McVersionClassifier.GetCategoryDisplayName(category)} ({versions.Count})",
Margin = new Thickness(0d, 0d, 0d, 15d)
};
var stack = _CreateVersionStack(versions);
card.Children.Add(stack);
card.SwapControl = stack;
// 不能使用 AddressOf,这导致了 #535,原因完全不明,疑似是编译器 Bug
card.InstallMethod = _StackInstall;
card.IsSwapped = true;
PanMinecraft.Children.Add(card);
}
}
private static StackPanel _CreateVersionStack(IEnumerable<JsonObject> versions)
{
return new StackPanel
{
Margin = new Thickness(20d, MyCard.SwapedHeight, 18d, 0d),
VerticalAlignment = VerticalAlignment.Top,
RenderTransform = new TranslateTransform(0d, 0d),
Tag = versions
};
}
private static void _StackInstall(StackPanel stack)
{
foreach (var item in (IEnumerable)stack.Tag)
stack.Children.Add(
ModDownloadLib.McDownloadListItem(
(JsonObject)item,
(sender, e) => ModMain.frmDownloadInstall.MinecraftSelected((MyListItem)sender, e),
false
)
);
}
/// <summary>
/// 当 MC 版本列表加载完时,立即自动选择的版本。用于外部调用。
/// </summary>
public static string mcVersionWaitingForSelect = null;
#endregion
#region OptiFine
/// <summary>
/// 获取 OptiFine 的加载异常信息。若正常则返回 Nothing。
/// </summary>
private string LoadOptiFineGetError()
{
if (selectedLoaderName == "NeoForge" || selectedLoaderName == "LabyMod" || selectedLoaderName == "Cleanroom")
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
if (LoadOptiFine is null || LoadOptiFine.State.LoadingState == MyLoading.MyLoadingState.Run)
return Lang.Text("Download.Install.State.Loading");
if (LoadOptiFine.State.LoadingState == MyLoading.MyLoadingState.Error)
return $"{Lang.Text("Download.Install.State.GetVersionListFailed")}{((ModLoader.LoaderBase)LoadOptiFine.State).Error.Message}";
// 检查 Forge 1.13 - 1.14.3:全部不兼容
if (selectedLoaderName == "Forge" && McVersionComparer.CompareVersion(_vanillaName, "1.13") >= 0 &&
McVersionComparer.CompareVersion("1.14.3", _vanillaName) >= 0) return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
// 检查 Fabric 1.20.5+: 全部不兼容
if (selectedFabric is not null && McVersionComparer.CompareVersion(_vanillaName, "1.20.4") > 0)
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
// 检查 Loader
if (GetLoaderError(LoadOptiFine) is not null)
return GetLoaderError(LoadOptiFine);
// 检查 Forge 版本
var hasAny = false;
var hasRequiredVersion = false;
foreach (var OptiFineVersion in ModDownload.dlOptiFineListLoader.output.Value)
{
if (!OptiFineVersion.DisplayName.StartsWith(_vanillaName + " "))
continue; // 不是同一个大版本
hasAny = true;
if (selectedForge is null)
return null; // 未选择 Forge
if ((bool)IsOptiFineSuitForForge(OptiFineVersion, selectedForge))
return null; // 该版本可用
if (OptiFineVersion.RequiredForgeVersion is not null)
hasRequiredVersion = true;
}
if (!hasAny) return Lang.Text("Download.Install.State.NoVersion");
if (hasRequiredVersion) return Lang.Text("Download.Install.Compat.CompatForgeSpecificOnly");
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
}
// 检查某个 OptiFine 是否与某个 Forge 兼容
private object IsOptiFineSuitForForge(ModDownload.DlOptiFineListEntry optiFine,
ModDownload.DlForgeVersionEntry forge)
{
if ((forge.Inherit ?? "") != (optiFine.Inherit ?? ""))
return false; // 不是同一个大版本
if (optiFine.RequiredForgeVersion is null)
return false; // 不兼容 Forge
if (string.IsNullOrWhiteSpace(optiFine.RequiredForgeVersion))
return true; // #4183
if (optiFine.RequiredForgeVersion.Contains(".")) // XX.X.XXX
return McVersionComparer.CompareVersion(forge.version.ToString(), optiFine.RequiredForgeVersion) == 0;
// XXXX
return forge.version.Revision == Convert.ToDouble(optiFine.RequiredForgeVersion);
}
// 限制展开
private void CardOptiFine_PreviewSwap(object sender, ModBase.RouteEventArgs e)
{
if (LoadOptiFineGetError() is not null)
e.handled = true;
}
/// <summary>
/// 尝试重新可视化 OptiFine 版本列表。
/// </summary>
private void OptiFine_Loaded()
{
try
{
if (ModDownload.dlOptiFineListLoader.State != ModBase.LoadState.Finished)
return;
// 获取版本列表
var versions = new List<ModDownload.DlOptiFineListEntry>();
foreach (var Version in ModDownload.dlOptiFineListLoader.output.Value)
{
if (selectedForge is not null &&
!(bool)IsOptiFineSuitForForge(Version, selectedForge))
continue;
if (Version.DisplayName.StartsWith(_vanillaName + " "))
versions.Add(Version);
}
if (!versions.Any())
return;
// 排序
versions.Sort((left, right) =>
{
if (!left.IsPreview && right.IsPreview)
return true;
if (left.IsPreview && !right.IsPreview)
return false;
return McVersionComparer.CompareVersionGe(left.DisplayName, right.DisplayName);
});
// 可视化
PanOptiFine.Children.Clear();
foreach (var Version in versions)
PanOptiFine.Children.Add(
ModDownloadLib.OptiFineDownloadListItem(Version, (a, b) => this.OptiFine_Selected((dynamic)a, b),
false));
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 OptiFine 安装版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Install.Error.OperationFailed"));
}
}
// 选择与清除
private void OptiFine_Selected(MyListItem sender, EventArgs e)
{
selectedOptiFine = (ModDownload.DlOptiFineListEntry)(dynamic)sender.Tag;
if (selectedForge is not null &&
!(bool)IsOptiFineSuitForForge(selectedOptiFine, selectedForge))
selectedForge = null;
OptiFabric_Loaded();
Forge_Loaded();
NeoForge_Loaded();
CardOptiFine.IsSwapped = true;
ReloadSelected();
}
private void OptiFine_Clear(object sender, MouseButtonEventArgs e)
{
selectedOptiFine = null;
selectedOptiFabric = null;
autoSelectedOptiFabric = false;
CardOptiFine.IsSwapped = true;
e.Handled = true;
Forge_Loaded();
NeoForge_Loaded();
ReloadSelected();
}
#endregion
#region LiteLoader
/// <summary>
/// 获取 LiteLoader 的加载异常信息。若正常则返回 Nothing。
/// </summary>
private string LoadLiteLoaderGetError()
{
// 检查 Loader
if (GetLoaderError(LoadLiteLoader) is not null)
return GetLoaderError(LoadLiteLoader);
if (selectedLoaderName == "NeoForge" || selectedLoaderName == "LegacyFabric" || selectedLoaderName == "LabyMod" || selectedLoaderName == "Cleanroom")
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
// 检查版本
return ModDownload.dlLiteLoaderListLoader.output.Value.Any(v => (v.Inherit ?? "") == (_vanillaName ?? ""))
? null
: Lang.Text("Download.Install.State.NoVersion");
}
// 限制展开
private void CardLiteLoader_PreviewSwap(object sender, ModBase.RouteEventArgs e)
{
if (LoadLiteLoaderGetError() is not null)
e.handled = true;
}
/// <summary>
/// 尝试重新可视化 LiteLoader 版本列表。
/// </summary>
private void LiteLoader_Loaded()
{
try
{
if (ModDownload.dlLiteLoaderListLoader.State != ModBase.LoadState.Finished)
return;
// 获取版本列表
var versions = new List<ModDownload.DlLiteLoaderListEntry>();
foreach (var Version in ModDownload.dlLiteLoaderListLoader.output.Value)
if ((Version.Inherit ?? "") == (_vanillaName ?? ""))
versions.Add(Version);
if (!versions.Any())
return;
// 可视化
PanLiteLoader.Children.Clear();
foreach (var Version in versions)
PanLiteLoader.Children.Add(ModDownloadLib.LiteLoaderDownloadListItem(Version,
(a, b) => this.LiteLoader_Selected((dynamic)a, b), false));
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 LiteLoader 安装版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Install.Error.OperationFailed"));
}
}
// 选择与清除
private void LiteLoader_Selected(MyListItem sender, EventArgs e)
{
selectedLiteLoader = (ModDownload.DlLiteLoaderListEntry)(dynamic)sender.Tag;
CardLiteLoader.IsSwapped = true;
ReloadSelected();
}
private void LiteLoader_Clear(object sender, MouseButtonEventArgs e)
{
selectedLiteLoader = null;
CardLiteLoader.IsSwapped = true;
e.Handled = true;
ReloadSelected();
}
#endregion
#region Forge
/// <summary>
/// 获取 Forge 的加载异常信息。若正常则返回 Nothing。
/// </summary>
private string LoadForgeGetError()
{
if (McVersionComparer.CompareVersionGe("1.5.1", _vanillaName) && McVersionComparer.CompareVersionGe(_vanillaName, "1.1"))
return Lang.Text("Download.Install.State.NoVersion");
if (selectedLoaderName is not null && !ReferenceEquals(selectedLoaderName, "Forge"))
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
// 检查 Loader
if (GetLoaderError(LoadForge) is not null)
return GetLoaderError(LoadForge);
var loader = (ModLoader.LoaderTask<string, List<ModDownload.DlForgeVersionEntry>>)LoadForge.State;
if ((_vanillaName ?? "") != (loader.input ?? ""))
return Lang.Text("Download.Install.State.Getting");
// 检查版本
foreach (var Version in loader.output)
{
if (Version.Category == "universal" || Version.Category == "client")
continue; // 跳过无法自动安装的版本
if (selectedNeoForge is not null || selectedFabric is not null)
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
if (selectedOptiFine is not null && McVersionComparer.CompareVersionGe(_vanillaName, "1.13") &&
McVersionComparer.CompareVersionGe("1.14.3", _vanillaName))
return Lang.Text("Download.Install.Compat.IncompatibleWithOptiFine"); // 1.13 ~ 1.14.3 OptiFine 检查
if (selectedOptiFine is not null && !(bool)IsOptiFineSuitForForge(selectedOptiFine, Version))
continue;
return null;
}
return Lang.Text("Download.Install.Compat.IncompatibleWithOptiFine");
}
// 限制展开
private void CardForge_PreviewSwap(object sender, ModBase.RouteEventArgs e)
{
if (LoadForgeGetError() is not null)
e.handled = true;
}
/// <summary>
/// 尝试重新可视化 Forge 版本列表。
/// </summary>
private void Forge_Loaded()
{
try
{
if (!LoadForge.State.IsLoader)
return;
var loader = (ModLoader.LoaderTask<string, List<ModDownload.DlForgeVersionEntry>>)LoadForge.State;
if ((_vanillaName ?? "") != (loader.input ?? ""))
return;
if (loader.State != ModBase.LoadState.Finished)
return;
// 获取要显示的版本
var versions = loader.output.ToList(); // 复制数组,以免 Output 在实例化后变空
if (!loader.output.Any())
return;
PanForge.Children.Clear();
versions = versions.Where(v =>
{
if (v.Category == "universal" || v.Category == "client")
return false; // 跳过无法自动安装的版本
if (selectedOptiFine is not null &&
!(bool)IsOptiFineSuitForForge(selectedOptiFine, v))
return false;
return true;
}).OrderByDescending(v => v).ToList();
ModDownloadLib.ForgeDownloadListItemPreload(PanForge, versions,
(a, b) => this.Forge_Selected((dynamic)a, b), false);
foreach (var Version in versions)
PanForge.Children.Add(
ModDownloadLib.ForgeDownloadListItem(Version, (a, b) => this.Forge_Selected((dynamic)a, b), false));
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 Forge 安装版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Install.Error.OperationFailed"));
}
}
// 选择与清除
private void Forge_Selected(MyListItem sender, EventArgs e)
{
selectedForge = (ModDownload.DlForgeVersionEntry)(dynamic)sender.Tag;
selectedLoaderName = "Forge";
CardForge.IsSwapped = true;
if (selectedOptiFine is not null &&
!(bool)IsOptiFineSuitForForge(selectedOptiFine, selectedForge))
selectedOptiFine = null;
OptiFine_Loaded();
ReloadSelected();
}
private void Forge_Clear(object sender, MouseButtonEventArgs e)
{
selectedForge = null;
selectedLoaderName = null;
CardForge.IsSwapped = true;
e.Handled = true;
OptiFine_Loaded();
ReloadSelected();
}
#endregion
#region NeoForge
/// <summary>
/// 获取 NeoForge 的加载异常信息。若正常则返回 Nothing。
/// </summary>
private string LoadNeoForgeGetError()
{
if (selectedOptiFine is not null)
return Lang.Text("Download.Install.Compat.IncompatibleWithOptiFine");
if (selectedLoaderName is not null && !ReferenceEquals(selectedLoaderName, "NeoForge"))
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
// 检查 Loader
if (GetLoaderError(LoadNeoForge) is not null)
return GetLoaderError(LoadNeoForge);
// 检查版本
return ModDownload.dlNeoForgeListLoader.output.Value.Any(v => (v.Inherit ?? "") == (_vanillaName ?? ""))
? null
: Lang.Text("Download.Install.State.NoVersion");
}
// 限制展开
private void CardNeoForge_PreviewSwap(object sender, ModBase.RouteEventArgs e)
{
if (LoadNeoForgeGetError() is not null)
e.handled = true;
}
/// <summary>
/// 尝试重新可视化 NeoForge 版本列表。
/// </summary>
private void NeoForge_Loaded()
{
try
{
// 获取版本列表
if (ModDownload.dlNeoForgeListLoader.State != ModBase.LoadState.Finished)
return;
var versions = ModDownload.dlNeoForgeListLoader.output.Value
.Where(v => (v.Inherit ?? "") == (_vanillaName ?? "")).ToList();
if (!versions.Any())
return;
// 可视化
PanNeoForge.Children.Clear();
ModDownloadLib.NeoForgeDownloadListItemPreload(PanNeoForge, versions,
(a, b) => this.NeoForge_Selected((dynamic)a, b),
false);
foreach (var Version in versions)
PanNeoForge.Children.Add(
ModDownloadLib.NeoForgeDownloadListItem(Version, (a, b) => this.NeoForge_Selected((dynamic)a, b),
false));
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 NeoForge 安装版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Install.Error.OperationFailed"));
}
}
// 选择与清除
private void NeoForge_Selected(MyListItem sender, EventArgs e)
{
selectedNeoForge = (ModDownload.DlNeoForgeListEntry)(dynamic)sender.Tag;
selectedLoaderName = "NeoForge";
CardNeoForge.IsSwapped = true;
OptiFine_Loaded();
ReloadSelected();
}
private void NeoForge_Clear(object sender, MouseButtonEventArgs e)
{
selectedNeoForge = null;
selectedLoaderName = null;
CardNeoForge.IsSwapped = true;
e.Handled = true;
OptiFine_Loaded();
ReloadSelected();
}
#endregion
#region Cleanroom
/// <summary>
/// 获取 Cleanroom 的加载异常信息。若正常则返回 Nothing。
/// </summary>
private string? LoadCleanroomGetError()
{
if (!_vanillaName.StartsWith("1."))
return Lang.Text("Download.Install.State.NoAvailableVersion");
if (selectedOptiFine is not null)
return Lang.Text("Download.Install.Compat.IncompatibleWithOptiFine");
if (selectedLoaderName is not null && selectedLoaderName != "Cleanroom")
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
if (selectedLiteLoader is not null)
return Lang.Text("Download.Install.Compat.IncompatibleWithLiteLoader");
// 检查 Loader
if (GetLoaderError(LoadCleanroom) is not null)
return GetLoaderError(LoadCleanroom);
// 检查版本
return ModDownload.dlCleanroomListLoader.output.Value.Any(v => (v.Inherit ?? "") == (_vanillaName ?? ""))
? null
: Lang.Text("Download.Install.State.NoVersion");
}
// 限制展开
private void CardCleanroom_PreviewSwap(object sender, ModBase.RouteEventArgs e)
{
if (LoadCleanroomGetError() is not null)
e.handled = true;
}
/// <summary>
/// 尝试重新可视化 Cleanroom 版本列表。
/// </summary>
private void Cleanroom_Loaded()
{
try
{
// 获取版本列表
if (ModDownload.dlCleanroomListLoader.State != ModBase.LoadState.Finished)
return;
var versions = ModDownload.dlCleanroomListLoader.output.Value
.Where(v => (v.Inherit ?? "") == (_vanillaName ?? "")).ToList();
if (!versions.Any())
return;
// 可视化
PanCleanroom.Children.Clear();
ModDownloadLib.CleanroomDownloadListItemPreload(PanCleanroom, versions,
(a, b) => this.Cleanroom_Selected((dynamic)a, b), false);
foreach (var Version in versions)
PanCleanroom.Children.Add(
ModDownloadLib.CleanroomDownloadListItem(Version, (a, b) => this.Cleanroom_Selected((dynamic)a, b),
false));
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 Cleanroom 安装版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Install.Error.OperationFailed"));
}
}
// 选择与清除
private void Cleanroom_Selected(MyListItem sender, EventArgs e)
{
selectedCleanroom = (ModDownload.DlCleanroomListEntry)(dynamic)sender.Tag;
selectedLoaderName = "Cleanroom";
CardCleanroom.IsSwapped = true;
OptiFine_Loaded();
ReloadSelected();
}
private void Cleanroom_Clear(object sender, MouseButtonEventArgs e)
{
selectedCleanroom = null;
selectedLoaderName = null;
CardCleanroom.IsSwapped = true;
e.Handled = true;
OptiFine_Loaded();
ReloadSelected();
}
#endregion
#region Fabric
/// <summary>
/// 获取 Fabric 的加载异常信息。若正常则返回 Nothing。
/// </summary>
private string LoadFabricGetError()
{
// 检查 OptiFine 1.20.5+:没有 OptiFabric 故全部不兼容
if (selectedOptiFine is not null && McVersionComparer.CompareVersionGe(_vanillaName, "1.20.5"))
return Lang.Text("Download.Install.Compat.IncompatibleWithOptiFine");
// 检查 Loader
if (GetLoaderError(LoadFabric) is not null)
return GetLoaderError(LoadFabric);
// 检查版本
foreach (JsonObject version in ModDownload.dlFabricListLoader.output.Value["game"].AsArray())
if ((version["version"].ToString() ?? "") ==
(_vanillaName.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3") ?? ""))
{
if (selectedLoaderName is not null && !ReferenceEquals(selectedLoaderName, "Fabric"))
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
return null;
}
return Lang.Text("Download.Install.State.NoVersion");
}
// 限制展开
private void CardFabric_PreviewSwap(object sender, ModBase.RouteEventArgs e)
{
if (LoadFabricGetError() is not null)
e.handled = true;
}
/// <summary>
/// 尝试重新可视化 Fabric 版本列表。
/// </summary>
private void Fabric_Loaded()
{
try
{
if (ModDownload.dlFabricListLoader.State != ModBase.LoadState.Finished)
return;
// 获取版本列表
var versions = (JsonArray)ModDownload.dlFabricListLoader.output.Value["loader"];
if (!versions.Any())
return;
// 可视化
PanFabric.Children.Clear();
PanFabric.Tag = versions;
CardFabric.SwapControl = PanFabric;
CardFabric.InstallMethod = stack =>
{
foreach (var item in (IEnumerable)stack.Tag)
stack.Children.Add(
ModDownloadLib.FabricDownloadListItem((JsonObject)item,
(a, b) => this.Fabric_Selected((dynamic)a, b)));
};
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 Fabric 安装版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Install.Error.OperationFailed"));
}
}
// 选择与清除
public void Fabric_Selected(MyListItem sender, EventArgs e)
{
ModBase.Log(((dynamic)sender.Tag).ToString());
selectedFabric = ((dynamic)sender.Tag)["version"].ToString();
selectedLoaderName = "Fabric";
FabricApi_Loaded();
OptiFabric_Loaded();
CardFabric.IsSwapped = true;
ReloadSelected();
}
private void Fabric_Clear(object sender, MouseButtonEventArgs e)
{
selectedFabric = null;
selectedFabricApi = null;
autoSelectedFabricApi = false;
selectedOptiFabric = null;
autoSelectedOptiFabric = false;
selectedLoaderName = null;
selectedAPIName = null;
CardFabric.IsSwapped = true;
e.Handled = true;
ReloadSelected();
}
#endregion
#region Fabric API
/// <summary>
/// 判断某 Fabric API 是否适配当前选择的原版版本。
/// </summary>
public bool IsFabricApiCompatible(ModComp.CompFile fabricApi)
{
var fabricApiName = fabricApi.DisplayName;
try
{
if (fabricApiName is null || _vanillaName is null)
return false;
var targetName = _vanillaName.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3").ToLower();
return fabricApi.RawGameVersions.Any(f => f == targetName);
}
catch (Exception ex)
{
ModBase.Log(ex, "判断 Fabric API 版本适配性出错(" + fabricApiName + ", " + _vanillaName + "");
return false;
}
}
/// <summary>
/// 获取 FabricApi 的加载异常信息。若正常则返回 Nothing。
/// </summary>
private string LoadFabricApiGetError()
{
// 检查 Loader
if (GetLoaderError(LoadFabricApi) is not null)
return GetLoaderError(LoadFabricApi);
if (ModDownload.dlFabricApiLoader.output is null)
return selectedFabric is null ? Lang.Text("Download.Install.Compat.RequiresFabric") : Lang.Text("Download.Install.State.Getting");
// 检查版本
if (ModDownload.dlFabricApiLoader.output.Any(f => IsFabricApiCompatible(f)))
return selectedFabric is null ? Lang.Text("Download.Install.Compat.RequiresFabric") : null;
return Lang.Text("Download.Install.State.NoVersion");
}
// 限制展开
private void CardFabricApi_PreviewSwap(object sender, ModBase.RouteEventArgs e)
{
if (LoadFabricApiGetError() is not null)
e.handled = true;
}
private bool autoSelectedFabricApi;
/// <summary>
/// 尝试重新可视化 FabricApi 版本列表。
/// </summary>
private void FabricApi_Loaded()
{
try
{
if (ModDownload.dlFabricApiLoader.State != ModBase.LoadState.Finished)
return;
if (_vanillaName is null || selectedFabric is null)
return;
// 获取版本列表
var versions = new List<ModComp.CompFile>();
foreach (var version in ModDownload.dlFabricApiLoader.output)
if (IsFabricApiCompatible(version))
{
if (!version.DisplayName.StartsWith("["))
{
ModBase.Log("[Download] 已特判修改 Fabric API 显示名:" + version.DisplayName, ModBase.LogLevel.Debug);
version.DisplayName = "[" + _vanillaName + "] " + version.DisplayName;
}
versions.Add(version);
}
if (!versions.Any())
return;
versions = versions.OrderByDescending(v => v.ReleaseDate).ToList();
// 可视化
PanFabricApi.Children.Clear();
foreach (var version in versions)
{
if (!IsFabricApiCompatible(version))
continue;
PanFabricApi.Children.Add(
ModDownloadLib.FabricApiDownloadListItem(version, (a, b) => this.FabricApi_Selected((dynamic)a, b)));
}
// 自动选择 Fabric API
if (!autoSelectedFabricApi)
{
autoSelectedFabricApi = true;
ModBase.Log($"[Download] 已自动选择 Fabric API{((MyListItem)PanFabricApi.Children[0]).Title}");
FabricApi_Selected((MyListItem)PanFabricApi.Children[0], null);
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 Fabric API 安装版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Install.Error.OperationFailed"));
}
}
// 选择与清除
private void FabricApi_Selected(MyListItem sender, EventArgs e)
{
selectedFabricApi = (ModComp.CompFile)(dynamic)sender.Tag;
selectedAPIName = "Fabric API";
CardFabricApi.IsSwapped = true;
ReloadSelected();
}
private void FabricApi_Clear(object sender, MouseButtonEventArgs e)
{
selectedFabricApi = null;
selectedAPIName = null;
CardFabricApi.IsSwapped = true;
e.Handled = true;
ReloadSelected();
}
#endregion
#region LegacyFabric
/// <summary>
/// 获取 LegacyFabric 的加载异常信息。若正常则返回 Nothing。
/// </summary>
private string LoadLegacyFabricGetError()
{
if (LoadLegacyFabric is null || LoadLegacyFabric.State.LoadingState == MyLoading.MyLoadingState.Run)
return Lang.Text("Download.Install.State.Loading");
if (LoadLegacyFabric.State.LoadingState == MyLoading.MyLoadingState.Error)
return $"{Lang.Text("Download.Install.State.GetVersionListFailed")}{((ModLoader.LoaderBase)LoadLegacyFabric.State).Error.Message}";
foreach (JsonObject Version in ModDownload.dlLegacyFabricListLoader.output.Value["game"].AsArray())
if ((Version["version"].ToString() ?? "") == (_vanillaName ?? ""))
{
if (selectedLiteLoader is not null)
return Lang.Text("Download.Install.Compat.IncompatibleWithLiteLoader");
if (selectedLoaderName is not null && !ReferenceEquals(selectedLoaderName, "LegacyFabric"))
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
return null;
}
return Lang.Text("Download.Install.State.NoVersion");
}
// 限制展开
private void CardLegacyFabric_PreviewSwap(object sender, ModBase.RouteEventArgs e)
{
if (LoadLegacyFabricGetError() is not null)
e.handled = true;
}
/// <summary>
/// 尝试重新可视化 LegacyFabric 版本列表。
/// </summary>
private void LegacyFabric_Loaded()
{
try
{
if (ModDownload.dlLegacyFabricListLoader.State != ModBase.LoadState.Finished)
return;
// 获取版本列表
var versions = (JsonArray)ModDownload.dlLegacyFabricListLoader.output.Value["loader"];
if (!versions.Any())
return;
// 可视化
PanLegacyFabric.Children.Clear();
PanLegacyFabric.Tag = versions;
CardLegacyFabric.SwapControl = PanLegacyFabric;
CardLegacyFabric.InstallMethod = stack =>
{
foreach (var item in (IEnumerable)stack.Tag)
stack.Children.Add(ModDownloadLib.LegacyFabricDownloadListItem((JsonObject)item,
(a, b) => this.LegacyFabric_Selected((dynamic)a, b)));
};
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 LegacyFabric 安装版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Install.Error.OperationFailed"));
}
}
// 选择与清除
public void LegacyFabric_Selected(MyListItem sender, EventArgs e)
{
selectedLegacyFabric = ((dynamic)sender.Tag)["version"].ToString();
selectedLoaderName = "LegacyFabric";
LegacyFabricApi_Loaded();
CardLegacyFabric.IsSwapped = true;
ReloadSelected();
}
private void LegacyFabric_Clear(object sender, MouseButtonEventArgs e)
{
selectedLegacyFabric = null;
selectedLegacyFabricApi = null;
autoSelectedLegacyFabricApi = false;
selectedLoaderName = null;
selectedAPIName = null;
CardLegacyFabric.IsSwapped = true;
e.Handled = true;
ReloadSelected();
}
#endregion
#region Legacy Fabric API
/// <summary>
/// 从显示名判断该 API 是否与某版本适配。
/// </summary>
public static bool IsSuitableLegacyFabricApi(List<string> supportVersions, string minecraftVersion)
{
try
{
if (supportVersions.Contains(minecraftVersion)) return true;
return false;
}
catch (Exception ex)
{
ModBase.Log(ex, "判断 Legacy Fabric API 版本适配性出错(" + supportVersions + ", " + minecraftVersion + "");
return false;
}
}
/// <summary>
/// 获取 LegacyFabricApi 的加载异常信息。若正常则返回 Nothing。
/// </summary>
private string LoadLegacyFabricApiGetError()
{
if (LoadLegacyFabricApi is null || LoadLegacyFabricApi.State.LoadingState == MyLoading.MyLoadingState.Run)
return Lang.Text("Download.Install.State.Loading");
if (LoadLegacyFabricApi.State.LoadingState == MyLoading.MyLoadingState.Error)
return $"{Lang.Text("Download.Install.State.GetVersionListFailed")}{((ModLoader.LoaderBase)LoadLegacyFabricApi.State).Error.Message}";
if (selectedAPIName is not null && !ReferenceEquals(selectedAPIName, "Legacy Fabric API"))
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedAPIName);
if (ModDownload.dlLegacyFabricApiLoader.output is null)
{
if (selectedLegacyFabric is null)
return Lang.Text("Download.Install.Compat.RequiresLegacyFabric");
return Lang.Text("Download.Install.State.Loading");
}
foreach (var Version in ModDownload.dlLegacyFabricApiLoader.output)
{
if (!IsSuitableLegacyFabricApi(Version.GameVersions, _vanillaName))
continue;
if (selectedLegacyFabric is null)
return Lang.Text("Download.Install.Compat.RequiresLegacyFabric");
return null;
}
return Lang.Text("Download.Install.State.NoVersion");
}
// 限制展开
private void CardLegacyFabricApi_PreviewSwap(object sender, ModBase.RouteEventArgs e)
{
if (LoadLegacyFabricApiGetError() is not null)
e.handled = true;
}
private bool autoSelectedLegacyFabricApi;
/// <summary>
/// 尝试重新可视化 LegacyFabricApi 版本列表。
/// </summary>
private void LegacyFabricApi_Loaded()
{
try
{
if (ModDownload.dlLegacyFabricApiLoader.State != ModBase.LoadState.Finished)
return;
if (_vanillaName is null || selectedLegacyFabric is null)
return;
// 获取版本列表
var versions = new List<ModComp.CompFile>();
foreach (var Version in ModDownload.dlLegacyFabricApiLoader.output)
if (IsSuitableLegacyFabricApi(Version.GameVersions, _vanillaName))
versions.Add(Version);
if (!versions.Any())
return;
versions = versions.OrderByDescending(v => v.ReleaseDate).ToList();
// 可视化
PanLegacyFabricApi.Children.Clear();
foreach (var Version in versions)
{
if (!IsSuitableLegacyFabricApi(Version.GameVersions, _vanillaName))
continue;
PanLegacyFabricApi.Children.Add(
ModDownloadLib.LegacyFabricApiDownloadListItem(Version,
(a, b) => this.LegacyFabricApi_Selected((dynamic)a, b)));
}
// 自动选择 Legacy Fabric API
if (!autoSelectedLegacyFabricApi)
{
autoSelectedLegacyFabricApi = true;
ModBase.Log($"[Download] 已自动选择 Legacy Fabric API{((MyListItem)PanLegacyFabricApi.Children[0]).Title}");
LegacyFabricApi_Selected((MyListItem)PanLegacyFabricApi.Children[0], null);
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 Legacy Fabric API 安装版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Install.Error.OperationFailed"));
}
}
// 选择与清除
private void LegacyFabricApi_Selected(MyListItem sender, EventArgs e)
{
selectedLegacyFabricApi = (ModComp.CompFile)(dynamic)sender.Tag;
selectedAPIName = "Legacy Fabric API";
CardLegacyFabricApi.IsSwapped = true;
ReloadSelected();
}
private void LegacyFabricApi_Clear(object sender, MouseButtonEventArgs e)
{
selectedLegacyFabricApi = null;
selectedAPIName = null;
CardLegacyFabricApi.IsSwapped = true;
e.Handled = true;
ReloadSelected();
}
#endregion
#region OptiFabric
/// <summary>
/// 判断某 OptiFabric 是否适配当前选择的原版版本。
/// </summary>
private bool IsOptiFabricCompatible(ModComp.CompFile modFile)
{
try
{
if (_vanillaName is null)
return false;
return modFile.GameVersions.Contains(_vanillaName);
}
catch (Exception ex)
{
ModBase.Log(ex, "判断 OptiFabric 版本适配性出错(" + _vanillaName + "");
return false;
}
}
private bool autoSelectedOptiFabric;
/// <summary>
/// 获取 OptiFabric 的加载异常信息。若正常则返回 Nothing。
/// </summary>
private string LoadOptiFabricGetError()
{
if (VanillaDrop >= 140 && VanillaDrop <= 150)
return Lang.Text("Download.Install.Compat.OptiFabricOriginsRequired");
// 检查 Loader
if (GetLoaderError(LoadOptiFabric) is not null)
return GetLoaderError(LoadOptiFabric);
// 检查版本
if (ModDownload.dlOptiFabricLoader.output is null)
{
if (selectedFabric is null && selectedOptiFine is null)
return Lang.Text("Download.Install.Compat.RequiresOptiFineAndFabric");
if (selectedFabric is null)
return Lang.Text("Download.Install.Compat.RequiresFabric");
if (selectedOptiFine is null)
return Lang.Text("Download.Install.Compat.RequiresOptiFine");
return Lang.Text("Download.Install.State.Getting");
}
foreach (var version in ModDownload.dlOptiFabricLoader.output)
{
if (!IsOptiFabricCompatible(version))
continue; // 2135#
if (selectedFabric is null && selectedOptiFine is null)
return Lang.Text("Download.Install.Compat.RequiresOptiFineAndFabric");
if (selectedFabric is null)
return Lang.Text("Download.Install.Compat.RequiresFabric");
if (selectedOptiFine is null)
return Lang.Text("Download.Install.Compat.RequiresOptiFine");
return null; // 通过检查
}
return Lang.Text("Download.Install.State.NoVersion");
}
// 限制展开
private void CardOptiFabric_PreviewSwap(object sender, ModBase.RouteEventArgs e)
{
if (LoadOptiFabricGetError() is not null)
e.handled = true;
}
/// <summary>
/// 尝试重新可视化 OptiFabric 版本列表。
/// </summary>
private void OptiFabric_Loaded()
{
try
{
if (ModDownload.dlOptiFabricLoader.State != ModBase.LoadState.Finished)
return;
if (_vanillaName is null || selectedFabric is null || selectedOptiFine is null)
return;
// 获取版本列表
var versions = new List<ModComp.CompFile>();
foreach (var Version in ModDownload.dlOptiFabricLoader.output)
if (IsOptiFabricCompatible(Version))
versions.Add(Version);
if (!versions.Any())
return;
// 排序
versions = versions.OrderByDescending(v => v.ReleaseDate).ToList();
// 可视化
PanOptiFabric.Children.Clear();
foreach (var Version in versions)
{
if (!IsOptiFabricCompatible(Version))
continue;
PanOptiFabric.Children.Add(
ModDownloadLib.OptiFabricDownloadListItem(Version,
(a, b) => this.OptiFabric_Selected((dynamic)a, b)));
}
// 自动选择 OptiFabric
if (autoSelectedOptiFabric || (VanillaDrop >= 140 && VanillaDrop <= 150))
return; // 1.14~15 不自动选择
autoSelectedOptiFabric = true;
ModBase.Log($"[Download] 已自动选择 OptiFabric{((MyListItem)PanOptiFabric.Children[0]).Title}");
OptiFabric_Selected((MyListItem)PanOptiFabric.Children[0], null);
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 OptiFabric 安装版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Install.Error.OperationFailed"));
}
}
// 选择与清除
private void OptiFabric_Selected(MyListItem sender, EventArgs e)
{
selectedOptiFabric = (ModComp.CompFile)(dynamic)sender.Tag;
CardOptiFabric.IsSwapped = true;
ReloadSelected();
}
private void OptiFabric_Clear(object sender, MouseButtonEventArgs e)
{
selectedOptiFabric = null;
CardOptiFabric.IsSwapped = true;
e.Handled = true;
ReloadSelected();
}
#endregion
#region LabyMod
/// <summary>
/// 获取 LabyMod 的加载异常信息。若正常则返回 Nothing。
/// </summary>
private string LoadLabyModGetError()
{
if (LoadLabyMod is null || LoadLabyMod.State.LoadingState == MyLoading.MyLoadingState.Run)
return Lang.Text("Download.Install.State.Loading");
if (LoadLabyMod.State.LoadingState == MyLoading.MyLoadingState.Error)
return $"{Lang.Text("Download.Install.State.GetVersionListFailed")}{((ModLoader.LoaderBase)LoadLabyMod.State).Error.Message}";
// 检查 Loader
if (GetLoaderError(LoadLabyMod) is not null)
return GetLoaderError(LoadLabyMod);
if (selectedOptiFine is not null)
return Lang.Text("Download.Install.Compat.IncompatibleWithOptiFine");
if (selectedLoaderName is not null && selectedLoaderName != "LabyMod")
return Lang.Text("Download.Install.Compat.IncompatibleWithLoader", selectedLoaderName);
if (selectedLiteLoader is not null)
return Lang.Text("Download.Install.Compat.IncompatibleWithLiteLoader");
foreach (JsonObject Version in ModDownload.dlLabyModListLoader.output.Value["production"]["minecraftVersions"].AsArray())
if ((Version["version"].ToString() ?? "") == (_vanillaName ?? ""))
return null;
foreach (JsonObject Version in ModDownload.dlLabyModListLoader.output.Value["snapshot"]["minecraftVersions"].AsArray())
if ((Version["version"].ToString() ?? "") == (_vanillaName ?? ""))
return null;
return Lang.Text("Download.Install.State.NoVersion");
}
// 限制展开
private void CardLabyMod_PreviewSwap(object sender, ModBase.RouteEventArgs e)
{
if (LoadLabyModGetError() is not null)
e.handled = true;
}
/// <summary>
/// 尝试重新可视化 LabyMod 版本列表。
/// </summary>
private void LabyMod_Loaded()
{
try
{
if (LoadLabyMod.State.LoadingState == MyLoading.MyLoadingState.Run)
return;
// 获取版本列表
var versions = ModDownload.dlLabyModListLoader.output.Value;
if (versions is null || versions["production"] is null || versions["snapshot"] is null)
return;
// 可视化
var processedVersions = new JsonArray();
foreach (JsonObject Production in versions["production"]["minecraftVersions"].AsArray())
if ((Production["version"].ToString() ?? "") == (_vanillaName ?? ""))
{
var productionVersion = new JsonObject();
productionVersion.Add("version", versions["production"]["labyModVersion"].ToString());
productionVersion.Add("channel", "production");
productionVersion.Add("commitReference", versions["production"]["commitReference"].ToString());
processedVersions.Add(productionVersion);
}
foreach (JsonObject Snapshot in versions["snapshot"]["minecraftVersions"].AsArray())
if ((Snapshot["version"].ToString() ?? "") == (_vanillaName ?? ""))
{
var snapshotVersion = new JsonObject();
snapshotVersion.Add("version", versions["snapshot"]["labyModVersion"].ToString());
snapshotVersion.Add("channel", "snapshot");
snapshotVersion.Add("commitReference", versions["snapshot"]["commitReference"].ToString());
processedVersions.Add(snapshotVersion);
}
// MyMsgBox(If(ProcessedVersions.ToString, "Nothing"))
PanLabyMod.Children.Clear();
PanLabyMod.Tag = processedVersions;
CardLabyMod.SwapControl = PanLabyMod;
CardLabyMod.InstallMethod = stack =>
{
foreach (JsonObject item in (IEnumerable)stack.Tag)
stack.Children.Add(
ModDownloadLib.LabyModDownloadListItem(item, (a, b) => this.LabyMod_Selected((dynamic)a, b)));
};
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 LabyMod 安装版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Install.Error.OperationFailed"));
}
}
// 选择与清除
public void LabyMod_Selected(MyListItem sender, EventArgs e)
{
selectedLabyModChannel = ((dynamic)sender.Tag)["channel"].ToString();
selectedLabyModCommitRef = ((dynamic)sender.Tag)["commitReference"].ToString();
selectedLabyModBaseVersion = ((dynamic)sender.Tag)["version"].ToString();
selectedLabyModVersion =
selectedLabyModBaseVersion + " " + (selectedLabyModChannel == "snapshot" ? Lang.Text("Download.Version.Type.Snapshot") : Lang.Text("Download.Version.Type.Stable"));
selectedLoaderName = "LabyMod";
CardLabyMod.IsSwapped = true;
ReloadSelected();
}
private void LabyMod_Clear(object sender, MouseButtonEventArgs e)
{
selectedLabyModCommitRef = null;
selectedLabyModVersion = null;
selectedLabyModBaseVersion = null;
selectedLabyModChannel = null;
if (selectedLoaderName == "LabyMod")
{
selectedLoaderName = null;
}
selectedAPIName = null;
CardLabyMod.IsSwapped = true;
e.Handled = true;
ReloadSelected();
}
#endregion
#region
private void TextSelectName_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && BtnStart.IsEnabled)
BtnStart_Click();
}
private void BtnStart_Click()
{
// 确认版本隔离
if (selectedLoaderName is not null &&
(Config.Launch.IndieSolutionV2 == 0 ||
Config.Launch.IndieSolutionV2 == 2))
if (ModMain.MyMsgBox(
Lang.Text("Download.Install.InstanceIsolation.Warning.Message"),
Lang.Text("Download.Install.InstanceIsolation.Warning.Title"),
Lang.Text("Download.Install.InstanceIsolation.Warning.Cancel"),
Lang.Text("Download.Install.InstanceIsolation.Warning.Continue")
) == 1)
return;
// 提交安装申请
var instanceName = TextSelectName.Text;
var request = new ModDownloadLib.McInstallRequest
{
targetInstanceName = instanceName,
targetInstanceFolder = $@"{ModFolder.mcFolderSelected}versions\{instanceName}\",
minecraftJson = _vanillaData?["url"].ToString(),
minecraftName = _vanillaName,
optiFineEntry = selectedOptiFine,
forgeEntry = selectedForge,
neoForgeEntry = selectedNeoForge,
cleanroomEntry = selectedCleanroom,
fabricVersion = selectedFabric,
fabricApi = selectedFabricApi,
optiFabric = selectedOptiFabric,
liteLoaderEntry = selectedLiteLoader,
labyModChannel = selectedLabyModChannel,
labyModCommitRef = selectedLabyModCommitRef,
legacyFabricVersion = selectedLegacyFabric,
legacyFabricApi = selectedLegacyFabricApi
};
if (!ModDownloadLib.McInstall(request))
return;
// 返回,这样在再次进入安装页面时这个实例就会显示文件夹已重复
ExitSelectPage();
}
#endregion
}
@@ -0,0 +1,37 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadLabyMod"
PanScroll="{Binding ElementName=PanBack}">
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
<StackPanel Orientation="Vertical" Margin="5">
<local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True"
Title="{DynamicResource Download.Version.LabyMod.Intro.Title}" x:Name="CardTip">
<StackPanel Margin="25,40,15,20">
<TextBlock Margin="0,0,0,11"
Text="{DynamicResource Download.Version.LabyMod.Intro.Description}"
TextWrapping="Wrap" />
<Grid Height="35">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
</Grid.ColumnDefinitions>
<local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140"
Text="{DynamicResource Download.Version.OpenWebsite}" Padding="13,0"
Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
</Grid>
</StackPanel>
</local:MyCard>
<local:MyCard x:Name="CardVersions" Margin="20,15,20,20"
Title="{DynamicResource Download.Version.VersionList}">
<StackPanel x:Name="PanVersions" Margin="20,40,18,20" />
</local:MyCard>
<local:MyCard HorizontalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False"
Margin="40,50">
<local:MyLoading Text="{DynamicResource Download.Version.LabyMod.LoadingList}" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</local:MyCard>
</StackPanel>
</local:MyScrollViewer>
</local:MyPageRight>
@@ -0,0 +1,70 @@
using PCL.Core.App.Localization;
namespace PCL;
public partial class PageDownloadLabyMod
{
public PageDownloadLabyMod()
{
Initialized += (_, _) => LoaderInit();
Loaded += (_, _) => Init();
InitializeComponent();
BtnWeb.Click += BtnWeb_Click;
}
private void LoaderInit()
{
PageLoaderInit(Load, PanLoad, CardVersions, CardTip, ModDownload.dlLabyModListLoader, _ => Load_OnFinish());
}
private void Init()
{
PanBack.ScrollToHome();
}
private void Load_OnFinish()
{
// 结果数据化
try
{
var versions = ModDownload.dlLabyModListLoader.output.Value;
if (versions is null)
return;
var productionEntry = new JsonObject();
productionEntry.Add("channel", "production");
productionEntry.Add("version", versions["production"]["labyModVersion"].ToString());
var snapshotEntry = new JsonObject();
snapshotEntry.Add("channel", "snapshot");
snapshotEntry.Add("version", versions["snapshot"]["labyModVersion"].ToString());
PanVersions.Children.Clear();
PanVersions.Children.Add(ModDownloadLib.LabyModDownloadListItem(productionEntry,
(a, b) => this.LabyMod_Production_Selected((MyListItem)a, b)));
PanVersions.Children.Add(ModDownloadLib.LabyModDownloadListItem(snapshotEntry,
(a, b) => this.LabyMod_Snapshot_Selected((MyListItem)a, b)));
CardVersions.Title = Lang.Text("Download.Version.VersionListCount", versions.Count);
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 LabyMod 版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Error.OperationFailed"));
}
}
private void LabyMod_Production_Selected(MyListItem sender, EventArgs e)
{
ModDownloadLib.McDownloadLabyModProductionLoaderSave();
}
private void LabyMod_Snapshot_Selected(MyListItem sender, EventArgs e)
{
ModDownloadLib.McDownloadLabyModSnapshotLoaderSave();
}
private void BtnWeb_Click(object sender, EventArgs e)
{
ModBase.OpenWebsite("https://labymod.net");
}
}
@@ -0,0 +1,287 @@
<local:MyPageLeft x:Class="PCL.PageDownloadLeft"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL">
<local:MyScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"
Style="{StaticResource ScrollViewerFullMargin}">
<StackPanel Margin="0,12,0,0" Name="PanItem">
<local:MyListItem x:Name="ItemInstall" Checked="True" IsScaleAnimationEnabled="False" Type="RadioBox"
Check="PageCheck"
Tag="1" MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.Minecraft}"
SvgIcon="lucide/boxes">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="1" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<TextBlock Text="{DynamicResource Download.Left.CommunityResources}" Margin="13,10,5,4" Opacity="0.6"
FontSize="12" />
<local:MyListItem x:Name="ItemMod" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="2"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.Mod}"
LogoScale="0.97"
SvgIcon="lucide/puzzle">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="2" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemPack" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="3"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.Modpack}"
LogoScale="0.98"
SvgIcon="lucide/package">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="3" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemDataPack" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="4"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.DataPack}"
LogoScale="0.96"
SvgIcon="lucide/file-archive">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="4" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemResourcePack" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="5"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.ResourcePack}"
LogoScale="0.81"
SvgIcon="lucide/layers">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="5" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemShader" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="6"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.Shader}"
LogoScale="1.04"
SvgIcon="lucide/sparkles">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="6" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemWorld" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="7"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.World}"
LogoScale="0.85"
SvgIcon="lucide/globe">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="7" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemFavorites" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="8"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.Favorites}"
SvgIcon="lucide/heart">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="8" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<TextBlock Text="{DynamicResource Download.Left.Installation}" Margin="13,10,5,4" Opacity="0.6" FontSize="12" />
<local:MyListItem x:Name="ItemClient" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="9"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.Minecraft}"
LogoScale="0.98"
SvgIcon="lucide/package">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="9" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemOptiFine" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="10"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.Optifine}"
LogoScale="1.00"
SvgIcon="lucide/gauge">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="10" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemForge" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="11"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.Forge}"
LogoScale="1.00"
SvgIcon="lucide/anvil">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="11" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemNeoForge" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="12"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.NeoForge}"
LogoScale="0.95"
SvgIcon="lucide/cat">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="12" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemCleanroom" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="13"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.Cleanroom}"
LogoScale="0.95"
SvgIcon="lucide/flask-conical">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="13" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemFabric" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="14"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.Fabric}"
LogoScale="1.02"
SvgIcon="lucide/scroll">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="14" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemLegacyFabric" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="18"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.LegacyFabric}"
LogoScale="1.02"
SvgIcon="lucide/scroll">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="18" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemLabyMod" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="17"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.LabyMod}"
LogoScale="1.02"
SvgIcon="lucide/box">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="17" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemLiteLoader" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="16"
Check="PageCheck"
MinPaddingRight="35" Height="36" VerticalAlignment="Top"
Title="{DynamicResource Download.Left.LiteLoader}"
LogoScale="1.06"
SvgIcon="lucide/egg">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="16" ToolTip="{DynamicResource Common.Action.Refresh}"
ToolTipService.Placement="Right" Click="RefreshButton_Click"
ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
LogoScale="0.85"
SvgIcon="lucide/refresh-cw" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
</StackPanel>
</local:MyScrollViewer>
</local:MyPageLeft>
@@ -0,0 +1,406 @@
using System.Windows.Controls;
using System.Windows.Input;
using PCL.Core.App.Localization;
namespace PCL;
public partial class PageDownloadLeft : IRefreshable
{
public void Refresh()
{
Refresh(ModMain.frmMain.PageCurrentSub);
}
// 强制刷新
public void RefreshButton_Click(object sender, EventArgs e) // 由边栏按钮匿名调用
{
Refresh((FormMain.PageSubType)ModBase.Val(((MyIconButton)sender).Tag));
}
public void Refresh(FormMain.PageSubType subType)
{
switch (subType)
{
case FormMain.PageSubType.DownloadInstall:
{
ModDownload.dlClientListLoader.Start(isForceRestart: true);
ModDownload.dlOptiFineListLoader.Start(isForceRestart: true);
ModDownload.dlForgeListLoader.Start(isForceRestart: true);
ModDownload.dlNeoForgeListLoader.Start(isForceRestart: true);
ModDownload.dlCleanroomListLoader.Start(isForceRestart: true);
ModDownload.dlLiteLoaderListLoader.Start(isForceRestart: true);
ModDownload.dlFabricListLoader.Start(isForceRestart: true);
ModDownload.dlLegacyFabricListLoader.Start(isForceRestart: true);
ModDownload.dlFabricApiLoader.Start(isForceRestart: true);
ModDownload.dlLegacyFabricApiLoader.Start(isForceRestart: true);
ModDownload.dlOptiFabricLoader.Start(isForceRestart: true);
ModDownload.dlLabyModListLoader.Start(isForceRestart: true);
ItemInstall.Checked = true;
break;
}
case FormMain.PageSubType.DownloadMod:
{
ModComp.compProjectCache.Clear();
ModComp.compFilesCache.Clear();
if (ModMain.frmDownloadMod is not null)
{
ModMain.frmDownloadMod.Content.storage = new ModComp.CompProjectStorage();
ModMain.frmDownloadMod.Content.page = 0;
ModMain.frmDownloadMod.PageLoaderRestart();
}
ItemMod.Checked = true;
break;
}
case FormMain.PageSubType.DownloadPack:
{
ModComp.compProjectCache.Clear();
ModComp.compFilesCache.Clear();
if (ModMain.frmDownloadPack is not null)
{
ModMain.frmDownloadPack.Content.storage = new ModComp.CompProjectStorage();
ModMain.frmDownloadPack.Content.page = 0;
ModMain.frmDownloadPack.PageLoaderRestart();
}
ItemPack.Checked = true;
break;
}
case FormMain.PageSubType.DownloadDataPack:
{
ModComp.compProjectCache.Clear();
ModComp.compFilesCache.Clear();
if (ModMain.frmDownloadDataPack is not null)
{
ModMain.frmDownloadDataPack.Content.storage = new ModComp.CompProjectStorage();
ModMain.frmDownloadDataPack.Content.page = 0;
ModMain.frmDownloadDataPack.PageLoaderRestart();
}
ItemDataPack.Checked = true;
break;
}
case FormMain.PageSubType.DownloadResourcePack:
{
ModComp.compProjectCache.Clear();
ModComp.compFilesCache.Clear();
if (ModMain.frmDownloadResourcePack is not null)
{
ModMain.frmDownloadResourcePack.Content.storage = new ModComp.CompProjectStorage();
ModMain.frmDownloadResourcePack.Content.page = 0;
ModMain.frmDownloadResourcePack.PageLoaderRestart();
}
ItemResourcePack.Checked = true;
break;
}
case FormMain.PageSubType.DownloadShader:
{
ModComp.compProjectCache.Clear();
ModComp.compFilesCache.Clear();
if (ModMain.frmDownloadShader is not null)
{
ModMain.frmDownloadShader.Content.storage = new ModComp.CompProjectStorage();
ModMain.frmDownloadShader.Content.page = 0;
ModMain.frmDownloadShader.PageLoaderRestart();
}
ItemShader.Checked = true;
break;
}
case FormMain.PageSubType.DownloadWorld:
{
ModComp.compProjectCache.Clear();
ModComp.compFilesCache.Clear();
if (ModMain.frmDownloadWorld is not null)
{
ModMain.frmDownloadWorld.Content.storage = new ModComp.CompProjectStorage();
ModMain.frmDownloadWorld.Content.page = 0;
ModMain.frmDownloadWorld.PageLoaderRestart();
}
ItemWorld.Checked = true;
break;
}
case FormMain.PageSubType.DownloadClient:
{
ModDownload.dlClientListLoader.Start(isForceRestart: true);
ItemClient.Checked = true;
break;
}
case FormMain.PageSubType.DownloadOptiFine:
{
ModDownload.dlOptiFineListLoader.Start(isForceRestart: true);
ItemOptiFine.Checked = true;
break;
}
case FormMain.PageSubType.DownloadForge:
{
ModDownload.dlForgeListLoader.Start(isForceRestart: true);
ItemForge.Checked = true;
break;
}
case FormMain.PageSubType.DownloadNeoForge:
{
ModDownload.dlNeoForgeListLoader.Start(isForceRestart: true);
ItemNeoForge.Checked = true;
break;
}
case FormMain.PageSubType.DownloadCleanroom:
{
ModDownload.dlCleanroomListLoader.Start(isForceRestart: true);
ItemCleanroom.Checked = true;
break;
}
case FormMain.PageSubType.DownloadLiteLoader:
{
ModDownload.dlLiteLoaderListLoader.Start(isForceRestart: true);
ItemLiteLoader.Checked = true;
break;
}
case FormMain.PageSubType.DownloadFabric:
{
ModDownload.dlFabricListLoader.Start(isForceRestart: true);
ItemFabric.Checked = true;
break;
}
case FormMain.PageSubType.DownloadLabyMod:
{
ModDownload.dlLabyModListLoader.Start(isForceRestart: true);
ItemLabyMod.Checked = true;
break;
}
case FormMain.PageSubType.DownloadLegacyFabric:
{
ModDownload.dlLegacyFabricListLoader.Start(isForceRestart: true);
ItemLegacyFabric.Checked = true;
break;
}
case FormMain.PageSubType.DownloadCompFavorites:
{
if (ModMain.frmDownloadCompFavorites is not null)
ModMain.frmDownloadCompFavorites.PageLoaderRestart();
ItemFavorites.Checked = true;
break;
}
}
HintService.Hint(Lang.Text("Download.Left.Hint.Refreshing"), log: false);
}
// 点击返回
private void ItemInstall_Click(object sender, MouseButtonEventArgs e)
{
if (!ItemInstall.Checked)
return;
ModMain.frmDownloadInstall.ExitSelectPage();
}
#region
/// <summary>
/// 当前页面的编号。
/// </summary>
public FormMain.PageSubType pageID = FormMain.PageSubType.DownloadInstall;
public PageDownloadLeft()
{
InitializeComponent();
ItemInstall.Check += PageCheck;
ItemMod.Check += PageCheck;
ItemPack.Check += PageCheck;
ItemDataPack.Check += PageCheck;
ItemResourcePack.Check += PageCheck;
ItemShader.Check += PageCheck;
ItemWorld.Check += PageCheck;
ItemFavorites.Check += PageCheck;
ItemClient.Check += PageCheck;
ItemOptiFine.Check += PageCheck;
ItemForge.Check += PageCheck;
ItemNeoForge.Check += PageCheck;
ItemLiteLoader.Check += PageCheck;
ItemFabric.Check += PageCheck;
ItemLegacyFabric.Check += PageCheck;
ItemLabyMod.Check += PageCheck;
AnimatedControl = PanItem;
}
/// <summary>
/// 勾选事件改变页面。
/// </summary>
private void PageCheck(object sender, ModBase.RouteEventArgs e)
{
if (sender is MyListItem { Tag: { } tag })
PageChange((FormMain.PageSubType)ModBase.Val(tag));
}
public object PageGet(FormMain.PageSubType id)
{
if (id == default)
id = pageID;
switch (id)
{
case FormMain.PageSubType.DownloadInstall:
{
if (ModMain.frmDownloadInstall is null)
ModMain.frmDownloadInstall = new PageDownloadInstall();
return ModMain.frmDownloadInstall;
}
case FormMain.PageSubType.DownloadMod:
{
if (ModMain.frmDownloadMod is null)
ModMain.frmDownloadMod = new PageDownloadMod();
return ModMain.frmDownloadMod;
}
case FormMain.PageSubType.DownloadPack:
{
if (ModMain.frmDownloadPack is null)
ModMain.frmDownloadPack = new PageDownloadPack();
return ModMain.frmDownloadPack;
}
case FormMain.PageSubType.DownloadDataPack:
{
if (ModMain.frmDownloadDataPack is null)
ModMain.frmDownloadDataPack = new PageDownloadDataPack();
return ModMain.frmDownloadDataPack;
}
case FormMain.PageSubType.DownloadResourcePack:
{
if (ModMain.frmDownloadResourcePack is null)
ModMain.frmDownloadResourcePack = new PageDownloadResourcePack();
return ModMain.frmDownloadResourcePack;
}
case FormMain.PageSubType.DownloadShader:
{
if (ModMain.frmDownloadShader is null)
ModMain.frmDownloadShader = new PageDownloadShader();
return ModMain.frmDownloadShader;
}
case FormMain.PageSubType.DownloadWorld:
{
if (ModMain.frmDownloadWorld is null)
ModMain.frmDownloadWorld = new PageDownloadWorld();
return ModMain.frmDownloadWorld;
}
case FormMain.PageSubType.DownloadCompFavorites:
{
if (ModMain.frmDownloadCompFavorites is null)
ModMain.frmDownloadCompFavorites = new PageDownloadCompFavorites();
return ModMain.frmDownloadCompFavorites;
}
case FormMain.PageSubType.DownloadClient:
{
if (ModMain.frmDownloadClient is null)
ModMain.frmDownloadClient = new PageDownloadClient();
return ModMain.frmDownloadClient;
}
case FormMain.PageSubType.DownloadOptiFine:
{
if (ModMain.frmDownloadOptiFine is null)
ModMain.frmDownloadOptiFine = new PageDownloadOptiFine();
return ModMain.frmDownloadOptiFine;
}
case FormMain.PageSubType.DownloadForge:
{
if (ModMain.frmDownloadForge is null)
ModMain.frmDownloadForge = new PageDownloadForge();
return ModMain.frmDownloadForge;
}
case FormMain.PageSubType.DownloadNeoForge:
{
if (ModMain.frmDownloadNeoForge is null)
ModMain.frmDownloadNeoForge = new PageDownloadNeoForge();
return ModMain.frmDownloadNeoForge;
}
case FormMain.PageSubType.DownloadCleanroom:
{
if (ModMain.frmDownloadCleanroom is null)
ModMain.frmDownloadCleanroom = new PageDownloadCleanroom();
return ModMain.frmDownloadCleanroom;
}
case FormMain.PageSubType.DownloadLiteLoader:
{
if (ModMain.frmDownloadLiteLoader is null)
ModMain.frmDownloadLiteLoader = new PageDownloadLiteLoader();
return ModMain.frmDownloadLiteLoader;
}
case FormMain.PageSubType.DownloadFabric:
{
if (ModMain.frmDownloadFabric is null)
ModMain.frmDownloadFabric = new PageDownloadFabric();
return ModMain.frmDownloadFabric;
}
case FormMain.PageSubType.DownloadLabyMod:
{
if (ModMain.frmDownloadLabyMod is null)
ModMain.frmDownloadLabyMod = new PageDownloadLabyMod();
return ModMain.frmDownloadLabyMod;
}
case FormMain.PageSubType.DownloadLegacyFabric:
{
if (ModMain.frmDownloadLegacyFabric is null)
ModMain.frmDownloadLegacyFabric = new PageDownloadLegacyFabric();
return ModMain.frmDownloadLegacyFabric;
}
default:
{
throw new Exception(Lang.Text("Download.Left.Error.UnknownSubPageType", (int)id));
}
}
}
/// <summary>
/// 切换现有页面。
/// </summary>
public void PageChange(FormMain.PageSubType id)
{
if (pageID == id)
return;
ModAnimation.AniControlEnabled += 1;
try
{
PageChangeRun((MyPageRight)PageGet(id));
pageID = id;
}
catch (Exception ex)
{
ModBase.Log(
ex,
"切换分页面失败(ID " + (int)id + "",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Error.OperationFailed"));
}
finally
{
ModAnimation.AniControlEnabled -= 1;
}
}
private static void PageChangeRun(MyPageRight target)
{
ModAnimation.AniStop("FrmMain PageChangeRight"); // 停止主页面的右页面切换动画,防止它与本动画一起触发多次 PageOnEnter
if (target.Parent is not null)
target.SetValue(ContentPresenter.ContentProperty, null);
ModMain.frmMain.pageRight = target;
((MyPageRight)ModMain.frmMain.PanMainRight.Child).PageOnExit();
ModAnimation.AniStart(new[]
{
ModAnimation.AaCode(() =>
{
((MyPageRight)ModMain.frmMain.PanMainRight.Child).PageOnForceExit();
ModMain.frmMain.PanMainRight.Child = ModMain.frmMain.pageRight;
ModMain.frmMain.pageRight.Opacity = 0d;
}, 130),
ModAnimation.AaCode(() =>
{
// 延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
ModMain.frmMain.pageRight.Opacity = 1d;
ModMain.frmMain.pageRight.PageOnEnter();
}, 30, true)
}, "PageLeft PageChange");
}
#endregion
}
@@ -0,0 +1,37 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadLegacyFabric"
PanScroll="{Binding ElementName=PanBack}">
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
<StackPanel Orientation="Vertical" Margin="5">
<local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True"
Title="{DynamicResource Download.Version.LegacyFabric.Intro.Title}" x:Name="CardTip">
<StackPanel Margin="25,40,15,20">
<TextBlock Margin="0,0,0,11"
Text="{DynamicResource Download.Version.LegacyFabric.Intro.Description}"
TextWrapping="Wrap" />
<Grid Height="35">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
</Grid.ColumnDefinitions>
<local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140"
Text="{DynamicResource Download.Version.OpenWebsite}" Padding="13,0"
Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
</Grid>
</StackPanel>
</local:MyCard>
<local:MyCard x:Name="CardVersions" Margin="20,15,20,20"
Title="{DynamicResource Download.Version.VersionList}">
<StackPanel x:Name="PanVersions" Margin="20,40,18,20" />
</local:MyCard>
<local:MyCard HorizontalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False"
Margin="40,50">
<local:MyLoading Text="{DynamicResource Download.Version.LegacyFabric.LoadingList}" Margin="20,20,20,17" x:Name="Load"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</local:MyCard>
</StackPanel>
</local:MyScrollViewer>
</local:MyPageRight>
@@ -0,0 +1,57 @@
using PCL.Core.App.Localization;
namespace PCL;
public partial class PageDownloadLegacyFabric
{
public PageDownloadLegacyFabric()
{
Initialized += (_, _) => LoaderInit();
Loaded += (_, _) => Init();
InitializeComponent();
BtnWeb.Click += BtnWeb_Click;
}
private void LoaderInit()
{
PageLoaderInit(Load, PanLoad, CardVersions, CardTip, ModDownload.dlLegacyFabricListLoader,
_ => Load_OnFinish());
}
private void Init()
{
PanBack.ScrollToHome();
}
private void Load_OnFinish()
{
// 结果数据化
try
{
var versions = (JsonArray)ModDownload.dlLegacyFabricListLoader.output.Value["installer"];
PanVersions.Children.Clear();
foreach (var Version in versions)
PanVersions.Children.Add(ModDownloadLib.LegacyFabricDownloadListItem((JsonObject)Version,
(a, b) => this.LegacyFabric_Selected((MyListItem)a, b)));
CardVersions.Title = Lang.Text("Download.Version.VersionListCount", versions.Count);
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 LegacyFabric 版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Error.OperationFailed"));
}
}
private void LegacyFabric_Selected(MyListItem sender, EventArgs e)
{
ModDownloadLib.McDownloadLegacyFabricLoaderSave((JsonObject)sender.Tag);
}
private void BtnWeb_Click(object sender, EventArgs e)
{
ModBase.OpenWebsite("https://legacyfabric.net/");
}
}
@@ -0,0 +1,34 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadLiteLoader"
PanScroll="{Binding ElementName=PanBack}">
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
<StackPanel Orientation="Vertical" Margin="5">
<local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True"
Title="{DynamicResource Download.Version.LiteLoader.Intro.Title}" x:Name="CardTip">
<StackPanel Margin="25,40,15,20">
<TextBlock Name="LabConnect" Margin="0,0,0,11"
Text="{DynamicResource Download.Version.LiteLoader.Intro.Description}"
TextWrapping="Wrap" />
<Grid Height="35">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
</Grid.ColumnDefinitions>
<local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140"
Text="{DynamicResource Download.Version.OpenWebsite}" Padding="13,0"
Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
</Grid>
</StackPanel>
</local:MyCard>
<StackPanel Name="PanMain" Margin="20,15,20,5" Grid.IsSharedSizeScope="True" />
<local:MyCard HorizontalAlignment="Center" Margin="40,50" SnapsToDevicePixels="True" x:Name="PanLoad"
UseAnimation="False">
<local:MyLoading Text="{DynamicResource Download.Version.LiteLoader.LoadingList}" Margin="20,20,20,17" x:Name="Load"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</local:MyCard>
</StackPanel>
</local:MyScrollViewer>
</local:MyPageRight>
@@ -0,0 +1,102 @@
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using PCL.Core.App.Localization;
namespace PCL;
public partial class PageDownloadLiteLoader
{
public PageDownloadLiteLoader()
{
Initialized += (_, _) => LoaderInit();
Loaded += (_, _) => Init();
InitializeComponent();
BtnWeb.Click += BtnWeb_Click;
}
private void LoaderInit()
{
PageLoaderInit(Load, PanLoad, PanMain, CardTip, ModDownload.dlLiteLoaderListLoader, _ => Load_OnFinish());
}
private void Init()
{
PanBack.ScrollToHome();
}
private void Load_OnFinish()
{
// 结果数据化
try
{
// 归类
var dict = new Dictionary<string, List<ModDownload.DlLiteLoaderListEntry>>();
for (var versionCode = 30; versionCode >= 0; versionCode -= 1)
dict.Add("1." + versionCode, new List<ModDownload.DlLiteLoaderListEntry>());
dict.Add(McVersionComparer.UNKNOWN_VERSION_KEY, []);
foreach (var Version in ModDownload.dlLiteLoaderListLoader.output.Value)
{
var mainVersion = "1." + Version.Inherit.Split(".")[1];
if (dict.ContainsKey(mainVersion))
dict[mainVersion].Add(Version);
else
dict[McVersionComparer.UNKNOWN_VERSION_KEY].Add(Version);
}
// 清空当前
PanMain.Children.Clear();
// 转化为 UI
foreach (var Pair in dict)
{
if (!Pair.Value.Any())
continue;
// 增加卡片
var newCard = new MyCard
{
Title = (Pair.Key == McVersionComparer.UNKNOWN_VERSION_KEY
? Lang.Text("Minecraft.Version.Unknown")
: Pair.Key) + " (" + Pair.Value.Count + ")",
Margin = new Thickness(0d, 0d, 0d, 15d)
};
var newStack = new StackPanel
{
Margin = new Thickness(20d, MyCard.SwapedHeight, 18d, 0d),
VerticalAlignment = VerticalAlignment.Top, RenderTransform = new TranslateTransform(0d, 0d),
Tag = Pair.Value
};
newCard.Children.Add(newStack);
newCard.SwapControl = newStack;
newCard.IsSwapped = true;
newCard.InstallMethod = stack =>
{
stack.Tag = ((List<ModDownload.DlLiteLoaderListEntry>)stack.Tag).Sort((a, b) =>
McVersionComparer.CompareVersion(a.Inherit, b.Inherit) == 1);
foreach (var item in (IEnumerable)stack.Tag)
stack.Children.Add(ModDownloadLib.LiteLoaderDownloadListItem(
(ModDownload.DlLiteLoaderListEntry)item, ModDownloadLib.LiteLoaderSave_Click, true));
};
PanMain.Children.Add(newCard);
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 LiteLoader 版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Error.OperationFailed"));
}
}
public void DownloadStart(MyListItem sender, object e)
{
ModDownloadLib.McDownloadLiteLoader((ModDownload.DlLiteLoaderListEntry)sender.Tag);
}
private void BtnWeb_Click(object sender, EventArgs e)
{
ModBase.OpenWebsite("https://www.liteloader.com");
}
}
@@ -0,0 +1,36 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadNeoForge"
PanScroll="{Binding ElementName=PanBack}">
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
<StackPanel Orientation="Vertical" Margin="5">
<local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True"
Title="{DynamicResource Download.Version.NeoForge.Intro.Title}" x:Name="CardTip">
<StackPanel Margin="25,40,15,20">
<TextBlock Margin="0,0,0,11"
Text="{DynamicResource Download.Version.NeoForge.Intro.Description}"
TextWrapping="Wrap" />
<Grid Height="35">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
</Grid.ColumnDefinitions>
<local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140"
Text="{DynamicResource Download.Version.OpenWebsite}" Padding="13,0"
Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight"
local:CustomEventService.EventType="OpenUrl"
local:CustomEventService.EventData="https://neoforged.net/" />
</Grid>
</StackPanel>
</local:MyCard>
<StackPanel Name="PanMain" Margin="20,15,20,5" Grid.IsSharedSizeScope="True" />
<local:MyCard HorizontalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False"
Margin="40,50">
<local:MyLoading Text="{DynamicResource Download.Version.NeoForge.LoadingList}" Margin="20,20,20,17" x:Name="Load"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</local:MyCard>
</StackPanel>
</local:MyScrollViewer>
</local:MyPageRight>
@@ -0,0 +1,73 @@
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using PCL.Core.App.Localization;
namespace PCL;
public partial class PageDownloadNeoForge
{
public PageDownloadNeoForge()
{
Initialized += (_, _) => LoaderInit();
Loaded += (_, _) => Init();
InitializeComponent();
}
private void LoaderInit()
{
PageLoaderInit(Load, PanLoad, PanMain, CardTip, ModDownload.dlNeoForgeListLoader, _ => Load_OnFinish());
}
private void Init()
{
PanBack.ScrollToHome();
}
private void Load_OnFinish()
{
// 结果数据化
try
{
// 归类
var dict = ModDownload.dlNeoForgeListLoader.output.Value.GroupBy(d => d.Inherit)
.OrderByDescending(g => g.Key).ToDictionary(g => g.Key, g => g.ToList());
// 清空当前
PanMain.Children.Clear();
// 转化为 UI
foreach (var Pair in dict)
{
if (!Pair.Value.Any())
continue;
// 增加卡片
var newCard = new MyCard
{ Title = Pair.Key + " (" + Pair.Value.Count + ")", Margin = new Thickness(0d, 0d, 0d, 15d) };
var newStack = new StackPanel
{
Margin = new Thickness(20d, MyCard.SwapedHeight, 18d, 0d),
VerticalAlignment = VerticalAlignment.Top, RenderTransform = new TranslateTransform(0d, 0d),
Tag = Pair.Value
};
newCard.Children.Add(newStack);
newCard.SwapControl = newStack;
newCard.IsSwapped = true;
newCard.InstallMethod = stack =>
{
foreach (var item in (IEnumerable)stack.Tag)
stack.Children.Add(ModDownloadLib.NeoForgeDownloadListItem(
(ModDownload.DlNeoForgeListEntry)item, ModDownloadLib.NeoForgeSave_Click, true));
};
PanMain.Children.Add(newCard);
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 NeoForge 版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Error.OperationFailed"));
}
}
}
@@ -0,0 +1,33 @@
<local:MyPageRight
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PCL.PageDownloadOptiFine"
PanScroll="{Binding ElementName=PanBack}">
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
<StackPanel Orientation="Vertical" Margin="5">
<local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True"
Title="{DynamicResource Download.Version.Optifine.Intro.Title}" x:Name="CardTip">
<StackPanel Margin="25,40,15,20">
<TextBlock Name="LabConnect" Margin="0,0,0,11"
Text="{DynamicResource Download.Version.Optifine.Intro.Description}" TextWrapping="Wrap" />
<Grid Height="35">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
</Grid.ColumnDefinitions>
<local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140"
Text="{DynamicResource Download.Version.OpenWebsite}" Padding="13,0"
Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
</Grid>
</StackPanel>
</local:MyCard>
<StackPanel Name="PanMain" Margin="20,15,20,5" Grid.IsSharedSizeScope="True" />
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="True"
x:Name="PanLoad" UseAnimation="False" Margin="40,50">
<local:MyLoading Text="{DynamicResource Download.Version.Optifine.LoadingList}" Margin="20,20,20,17" x:Name="Load"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</local:MyCard>
</StackPanel>
</local:MyScrollViewer>
</local:MyPageRight>
@@ -0,0 +1,101 @@
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using PCL.Core.App.Localization;
namespace PCL;
public partial class PageDownloadOptiFine
{
public PageDownloadOptiFine()
{
Initialized += (_, _) => LoaderInit();
Loaded += (_, _) => Init();
InitializeComponent();
BtnWeb.Click += BtnWeb_Click;
}
private void LoaderInit()
{
PageLoaderInit(Load, PanLoad, PanMain, CardTip, ModDownload.dlOptiFineListLoader, _ => Load_OnFinish());
}
private void Init()
{
PanBack.ScrollToHome();
}
private void Load_OnFinish()
{
// 结果数据化
try
{
const string snapshotKey = "Snapshot";
// 归类
var dict = new Dictionary<string, List<ModDownload.DlOptiFineListEntry>>();
dict.Add(snapshotKey, new List<ModDownload.DlOptiFineListEntry>());
for (var versionCode = 50; versionCode >= 0; versionCode -= 1)
dict.Add("1." + versionCode, new List<ModDownload.DlOptiFineListEntry>());
foreach (var Version in ModDownload.dlOptiFineListLoader.output.Value)
if (Version.Inherit.StartsWith("1."))
{
var mainVersion = "1." + Version.DisplayName.Split(".")[1].Split(" ")[0];
if (dict.ContainsKey(mainVersion))
dict[mainVersion].Add(Version);
else
dict[snapshotKey].Add(Version);
}
else
{
dict[snapshotKey].Add(Version);
}
// 清空当前
PanMain.Children.Clear();
// 转化为 UI
foreach (var Pair in dict)
{
if (!Pair.Value.Any())
continue;
// 增加卡片
var title = Pair.Key == snapshotKey
? Lang.Text("Download.Version.Optifine.Snapshot")
: Pair.Key;
var newCard = new MyCard
{ Title = title + " (" + Pair.Value.Count + ")", Margin = new Thickness(0d, 0d, 0d, 15d) };
var newStack = new StackPanel
{
Margin = new Thickness(20d, MyCard.SwapedHeight, 18d, 0d),
VerticalAlignment = VerticalAlignment.Top, RenderTransform = new TranslateTransform(0d, 0d),
Tag = Pair.Value
};
newCard.Children.Add(newStack);
newCard.SwapControl = newStack;
newCard.IsSwapped = true;
newCard.InstallMethod = stack =>
{
stack.Tag = ((List<ModDownload.DlOptiFineListEntry>)stack.Tag).Sort((a, b) =>
McVersionComparer.CompareVersion(a.DisplayName, b.DisplayName) == 1);
foreach (var item in (IEnumerable)stack.Tag)
stack.Children.Add(ModDownloadLib.OptiFineDownloadListItem(
(ModDownload.DlOptiFineListEntry)item, ModDownloadLib.OptiFineSave_Click, true));
};
PanMain.Children.Add(newCard);
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"可视化 OptiFine 版本列表出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Download.Error.OperationFailed"));
}
}
private void BtnWeb_Click(object sender, EventArgs e)
{
ModBase.OpenWebsite("https://www.optifine.net/");
}
}