初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -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
|
||||
}
|
||||
+67
@@ -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>
|
||||
+1183
@@ -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
|
||||
}
|
||||
+27
@@ -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>
|
||||
+9
@@ -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>
|
||||
+10
@@ -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>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageDownloadPack
|
||||
{
|
||||
public PageDownloadPack()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
+46
@@ -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>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
namespace PCL;
|
||||
|
||||
public partial class PageDownloadResourcePack
|
||||
{
|
||||
public PageDownloadResourcePack()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
+30
@@ -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>
|
||||
+9
@@ -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>
|
||||
+9
@@ -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>
|
||||
+80
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -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>
|
||||
+915
@@ -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>
|
||||
-->
|
||||
+2327
@@ -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
|
||||
}
|
||||
+37
@@ -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>
|
||||
+57
@@ -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>
|
||||
+102
@@ -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>
|
||||
+73
@@ -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>
|
||||
+101
@@ -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/");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user