Compare commits

...
47 Commits
Author SHA1 Message Date
LTCat 6b9a50df91 2.99.0.0 正式版 2026-10-20 14:51:42 +08:00
LTCat e4b0489a22 2.99.0.0 2026-10-20 14:33:23 +08:00
LTCat 5ef81e490a 2.14.0.0 正式版 2026-10-19 23:50:04 +08:00
LTCat ade6e04c6e 2.14.0.0 2026-10-17 03:39:13 +08:00
HerobrineXia 9dcb799fb0 Merge pull request #9452 from camileko/close-9450
fix: 修复没有 单个自定义事件 的情况下无法触发自定义事件列表
2026-10-14 21:51:02 +08:00
HerobrineXia 073546ba19 Merge pull request #9447 from camileko/close-9442
fix: 联网主页地址含有替换标记时主页无法显示
2026-10-14 19:51:45 +08:00
HerobrineXia f8584954b8 Merge pull request #9469 from RyogiMutsuki/fix/naudio/license
fix(license): 修复 NAudio 许可证问题
2026-10-13 02:25:52 +08:00
HerobrineXia a91d5e0079 Merge pull request #9445 from camileko/close-9254
fix: 修复窗口大小为最大化时启动高版本 Minecraft 会出现 Hidden Utility Window
2026-10-13 02:18:35 +08:00
HerobrineXia 5dcbd570a8 Merge pull request #9414 from camileko/close-9413
feat(cape): 添加 Aurora Cape 披风翻译
2026-10-13 00:34:31 +08:00
Ryogi Mutsuki 87a89e877d merge
fix(license): use hash ref to fixed license
2026-10-11 01:57:46 +08:00
Ryogi Mutsuki bc4ed0797f fix(license): use hash ref to fixed license 2026-10-11 01:56:11 +08:00
camileko b4a8193bae 修复没有单个自定义事件的情况下无法触发自定义事件列表 2026-10-06 00:09:53 +08:00
camileko 0a3258974a 联网主页地址含有替换标记时主页无法显示 2026-10-05 04:33:43 +08:00
unknown d69ef35547 把 Hidden Utility Window 加进窗口名黑名单 2026-10-05 00:18:13 +08:00
unknown 1ca8b78388 quick fix 2026-09-29 21:20:27 +08:00
龙腾猫跃 0e0d12fdce Merge pull request #9383 from camileko/close-9380
fix: 修复剪贴板被占用时无提示
2026-09-28 19:04:13 +08:00
HerobrineXia 565b49321f Merge pull request #9356 from camileko/close-8679
fix: 修复使用中文搜索 Mod、同时指定版本和加载器时,Modrinth 可能出现分别匹配但实际上没有对应版本的结果
2026-09-25 00:35:49 +08:00
HerobrineXia 8f4e6bbe05 Merge pull request #9378 from camileko/close-9362
fix: 修复失败和取消以外多余的1s等待
2026-09-24 23:49:03 +08:00
camileko d1e969d742 修复剪贴板被占用时无提示 2026-09-22 23:25:55 +08:00
camileko 6d5c73fe98 修复失败和取消以外多余的1s等待 2026-09-21 01:06:18 +08:00
camileko 8122a65e86 修复使用中文搜索 Mod、同时指定版本和加载器时,Modrinth 可能出现分别匹配但实际上没有对应版本的结果 2026-09-13 23:50:59 +08:00
龙腾猫跃 8f76864574 Merge pull request #9198 from Howard302/feature/esc-cancel-launch
feat: 在启动页面按 Esc 取消启动游戏
2026-09-03 21:39:12 +08:00
HerobrineXia 06125e2b89 Merge pull request #9220 from Howard302/feature/zip-drag-drop-hint
feat: 压缩包拖入时先显示检测提示
2026-08-28 15:19:07 +08:00
HerobrineXia 39c49c8b9d Merge pull request #9236 from Howard302/feat/add-open-custom.xaml-button
feat: 页面添加打开自定义XAML所在文件夹按钮
2026-08-27 16:49:14 +08:00
HerobrineXia 8230882954 Merge pull request #9133 from Howard302/fix/move-ssl-cert-option
feat:将"在正版登录时验证 SSL 证书"选项从"下载"卡片移动到"启动选项"
2026-08-27 15:20:06 +08:00
HerobrineXia 87cd3bc69d Merge pull request #9240 from ailiujiarui/codex/fix-issue-9239-java25-fabric
fix: 避免旧版 Fabric 自动选择 Java 25
2026-08-26 19:55:24 +08:00
HerobrineXia 4759945743 Merge pull request #9149 from Neclyon/chore/update-mgh-addr
chore: 更新杂志主页地址
2026-08-26 15:08:58 +08:00
Zhengyuan Huang 092b2a2edd fix: 对不起啦,忘记改ID会影响仓库地址了 2026-08-25 02:04:56 +08:00
pih 140348e10d chore: remove issue design note 2026-08-19 19:56:42 +08:00
pih 05c8a47a06 fix: avoid Java 25 for legacy Fabric 2026-08-19 15:27:37 +08:00
Howard302 b8651f109f feat: 页面添加打开自定义XAML所在文件夹按钮 2026-08-19 11:05:00 +08:00
PCL Contributor e5d4db3a6d feat: 压缩包拖入时先显示检测提示 2026-08-16 09:26:41 +00:00
Howard302 7c75fe4d4b feat: 在启动页面按 Esc 取消启动游戏
- 在 ModLaunch.vb 中添加 McLaunchCancel() 公共方法,封装取消启动逻辑
- 更新 PageLaunchLeft.vb 的 BtnCancel_Click 调用 McLaunchCancel()
- 更新 FormMain.xaml.vb 的 Esc 键处理:启动中按 Esc 取消启动,否则返回上一级

Closes #9193
2026-08-13 10:45:09 +00:00
CreeperIsASpy 9afbfe3f64 chore: 更新杂志主页地址 2026-08-08 18:11:46 +08:00
龙腾猫跃 639de1b48a Update PCL download link in README 2026-08-08 12:44:28 +08:00
LTCat 904de2af71 2.13.1.1 正式版 2026-08-07 19:54:54 +08:00
Howard302 2fe1ceb40f 将"在正版登录时验证 SSL 证书"选项从"下载"卡片移动到"启动选项"
Fixes #9119

将"在正版登录时验证 SSL 证书"选项从 设置-其他 的"下载"卡片移动到
设置-启动 的"启动选项"卡片中,因为该选项与下载无关,而与启动游戏
时的正版登录相关,在语义上更符合。
2026-08-07 05:52:41 +00:00
LTCat 0ce35fe7e5 2.13.1.1 2026-08-06 22:30:13 +08:00
LTCat 7fb6e5d68e 2.13.1.0 2026-08-02 01:00:16 +08:00
龙腾猫跃 c7ccdd65dc 将分发优先许可外链到石墨 2026-08-01 19:52:00 +08:00
龙腾猫跃 bbb4fc31a8 Merge pull request #8994 from qinghuayuci/devin/1784297153-fix-validatefolderpath-8dot3
fix: NTFS 8.3 文件名检查应校验各路径段而非整个路径
2026-07-18 00:56:36 +08:00
qinghuayuci d6c8266c58 fix: NTFS 8.3 文件名检查应校验各路径段而非整个路径 2026-07-17 14:07:36 +00:00
龙腾猫跃 217d34a237 Merge pull request #5767 from Open-KFC/main
fix #5744: 模组下载界面显示Tooltip时滚动鼠标致使该Tooltip被永远隐藏
2026-07-17 12:34:13 +08:00
龙腾猫跃 671cf8bdf5 Merge branch 'main' into main 2026-07-17 12:34:06 +08:00
龙腾猫跃 fd7b722346 放宽条件:若 PCL 不是收费主体,也允许收费分发 2026-07-11 10:22:00 +08:00
龙腾猫跃 cf7f8cbe19 2.13.0.1 正式版 2026-07-06 00:54:41 +08:00
Open-KFC 2a15bcf2c6 fix(MyScrollViewer): 滚动鼠标致使显示的ToolTip永远不可见 2025-05-24 20:00:52 +08:00
105 changed files with 9576 additions and 3250 deletions

No files matched your search

+1 -14
View File
@@ -1,19 +1,6 @@
《PCL 分发有限许可》
版权所有 © 成都瓜皮龙科技有限公司(以下简称 “许可方”)。
除本许可明确授予的权利外,许可方保留软件及其相关知识产权中的全部权利。
一、定义
“软件” 是指 Plain Craft Launcher(以下简称 “PCL”)及许可方有权许可的相关源代码、二进制文件、资源文件、文档和随附内容。软件不包括已明确标注为第三方内容的部分,本许可亦不限制第三方内容原有许可证所明确授予的权利。
“原样副本” 是指未经任何方式改变的软件副本。传输、存储、镜像、压缩或解压原样副本,不视为改变。
“收费” 是指以获得、下载、复制、分发、安装、预装或使用原样副本为对价或条件,直接或间接向接收者收取费用,包括但不限于付费下载、会员访问门槛、强制赞助或其他变相收费方式。承载平台通过与获得原样副本无直接对应关系的一般广告、一般赞助、平台服务或其他独立业务取得收入,不视为收费。
本许可适用于对软件进行复制、分发或其他被明确授权的行为。您行使本许可明确授予的任何权利,即表示同意遵守本许可。
二、权利
在持续不收费的前提下,许可方授予您一项全球范围内、非排他、免版税、不得再许可的有限许可:您可以存储、镜像、备份、上传、下载、向公众提供、传播和再分发原样副本。
每一位从您处获得原样副本的接收者,均可在遵守本许可的前提下,直接从许可方获得本许可明确授予的相应权利。
详见 https://shimo.im/docs/rGrd8pY8xWkt6ryW#anchor-X0bo
————————————————————————————————————
+246
View File
@@ -0,0 +1,246 @@
# 如果要从更高级别的目录继承 .editorconfig 设置,请删除以下行
root = true
# c# 文件
[*.cs]
#### Core EditorConfig 选项 ####
# 缩进和间距
indent_size = 4
indent_style = space
tab_width = 4
# 新行首选项
end_of_line = crlf
insert_final_newline = false
#### .NET 代码操作 ####
# 类型成员
dotnet_hide_advanced_members = false
dotnet_member_insertion_location = with_other_members_of_the_same_kind
dotnet_property_generation_behavior = prefer_throwing_properties
# 符号搜索
dotnet_search_reference_assemblies = true
#### .NET 编码约定 ####
# 组织 Using
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# this. 和 Me. 首选项
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# 语言关键字与 bcl 类型首选项
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# 括号首选项
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
# 修饰符首选项
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# 表达式级首选项
dotnet_prefer_system_hash_code = true
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_collection_expression = when_types_loosely_match
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = true
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# 字段首选项
dotnet_style_readonly_field = true
# 参数首选项
dotnet_code_quality_unused_parameters = all
# 禁止显示首选项
dotnet_remove_unnecessary_suppression_exclusions = none
# 新行首选项
dotnet_style_allow_multiple_blank_lines_experimental = true
dotnet_style_allow_statement_immediately_after_block_experimental = true
#### c# 编码约定 ####
# var 首选项
csharp_style_var_elsewhere = false
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = false
# Expression-bodied 成员
csharp_style_expression_bodied_accessors = true
csharp_style_expression_bodied_constructors = false
csharp_style_expression_bodied_indexers = true
csharp_style_expression_bodied_lambdas = true
csharp_style_expression_bodied_local_functions = false
csharp_style_expression_bodied_methods = false
csharp_style_expression_bodied_operators = false
csharp_style_expression_bodied_properties = true
# 模式匹配首选项
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true:suggestion
csharp_style_prefer_switch_expression = true
# Null 检查首选项
csharp_style_conditional_delegate_call = true
# 修饰符首选项
csharp_prefer_static_anonymous_function = true
csharp_prefer_static_local_function = true
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
csharp_style_prefer_readonly_struct = true
csharp_style_prefer_readonly_struct_member = true
# 代码块首选项
csharp_prefer_braces = true
csharp_prefer_simple_using_statement = true
csharp_prefer_system_threading_lock = true
csharp_style_namespace_declarations = file_scoped:suggestion
csharp_style_prefer_method_group_conversion = true
csharp_style_prefer_primary_constructors = true
csharp_style_prefer_top_level_statements = true
# 表达式级首选项
csharp_prefer_simple_default_expression = true
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = true
csharp_style_inlined_variable_declaration = true
csharp_style_prefer_implicitly_typed_lambda_expression = true
csharp_style_prefer_index_operator = true
csharp_style_prefer_local_over_anonymous_function = true
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_range_operator = true
csharp_style_prefer_tuple_swap = true
csharp_style_prefer_unbound_generic_type_in_nameof = true
csharp_style_prefer_utf8_string_literals = true
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable
# "using" 指令首选项
csharp_using_directive_placement = outside_namespace
# 新行首选项
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
csharp_style_allow_embedded_statements_on_same_line_experimental = true
#### C# 格式规则 ####
# 新行首选项
csharp_new_line_before_catch = false
csharp_new_line_before_else = false
csharp_new_line_before_finally = false
csharp_new_line_before_members_in_anonymous_types = false
csharp_new_line_before_members_in_object_initializers = false
csharp_new_line_before_open_brace = none
csharp_new_line_between_query_expression_clauses = false
# 缩进首选项
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = false
csharp_indent_labels = flush_left
csharp_indent_switch_labels = false
# 空格键首选项
csharp_space_after_cast = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = ignore
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# 包装首选项
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### 命名样式 ####
# 命名规则
dotnet_naming_rule.interface_should_be_以_i_开始.severity = suggestion
dotnet_naming_rule.interface_should_be_以_i_开始.symbols = interface
dotnet_naming_rule.interface_should_be_以_i_开始.style = 以_i_开始
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
# 符号规范
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.类型.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.类型.required_modifiers =
dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method
dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.非字段成员.required_modifiers =
# 命名样式
dotnet_naming_style.帕斯卡拼写法.required_prefix =
dotnet_naming_style.帕斯卡拼写法.required_suffix =
dotnet_naming_style.帕斯卡拼写法.word_separator =
dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
dotnet_naming_style.以_i_开始.required_prefix = I
dotnet_naming_style.以_i_开始.required_suffix =
dotnet_naming_style.以_i_开始.word_separator =
dotnet_naming_style.以_i_开始.capitalization = pascal_case
+23 -6
View File
@@ -1,11 +1,28 @@
namespace PCLCS;
public static class Configs {
/// <summary>当前 Java 相关配置的版本。0 代表尚未从老的 Settings 系统中迁移。</summary>
public static readonly ConfigEntry<int> JavaConfigVersion = new(nameof(JavaConfigVersion), 0);
public static readonly ConfigEntry<ConcurrentList<Java>> JavaList = new(nameof(JavaList), []);
public static readonly ConfigEntry<List<string>> JavaRemovedList = new(nameof(JavaRemovedList), []);
public static readonly ConfigEntry<bool> InstanceMigratedJava = new(nameof(InstanceMigratedJava), false);
public static readonly ConfigEntry<Java> InstanceForcedJava = new(nameof(InstanceForcedJava), null);
public static readonly DynamicConfigEntry<McInstallManifest> McInstallManifest = new("MinecraftInstallManifest", null);
// Java
/// <summary> 当前 Java 相关配置的版本。0 代表尚未从老的 Settings 系统中迁移。</summary>
public static readonly ConfigEntry<int> JavaConfigVersion = new("JavaConfigVersion", 0);
public static readonly ConfigEntry<ConcurrentList<Java>> JavaList = new("JavaList", []);
public static readonly ConfigEntry<List<string>> JavaRemovedList = new("JavaRemovedList", []);
public static readonly DynamicConfigEntry<bool> JavaMigrated = new("InstanceMigratedJava", false);
public static readonly DynamicConfigEntry<Java> JavaForced = new("InstanceForcedJava", null);
// 提示
public static readonly ConfigEntry<bool> HintModifyInstanceWarning = new("HintModifyInstanceWarning", false);
static Configs() {
/// 当 JavaForced 获取到一个不在 JavaList 中的值时,将其重置为 null。
JavaForced.PreviewGet += (args, provider) => {
if (args.Value is not null && !JavaList.Get()!.Contains(args.Value)) {
args.Value = null;
JavaForced.Reset(provider);
}
};
}
}
+10 -5
View File
@@ -17,10 +17,12 @@ public enum ResourceTypes {
DataPack = 16,
/// <summary>服务端插件。</summary>
Plugin = 32,
/// <summary>地图。</summary>
Map = 64,
/// <summary>同时包含数据包以及 Mod。</summary>
ModOrDataPack = Mod | DataPack,
/// <summary>允许任意种类,或种类未知。</summary>
Any = Mod | ModPack | ResourcePack | Shader | DataPack | Plugin,
Any = Mod | ModPack | ResourcePack | Shader | DataPack | Plugin | Map,
}
/// <summary>
@@ -42,9 +44,8 @@ public enum ModLoaders {
Forge = 1,
LiteLoader = 2,
Fabric = 4,
Quilt = 8,
NeoForge = 16,
All = Forge | LiteLoader | Fabric | Quilt | NeoForge,
All = Forge | LiteLoader | Fabric | NeoForge,
}
/// <summary>
@@ -67,13 +68,17 @@ public static class Versions {
/// <summary>
/// 版本管理中 Mod 信息缓存的版本号。
/// </summary>
public const int LocalModCacheVersion = 17;
public const int LocalModCacheVersion = 21;
/// <summary>
/// Minecraft 本地实例信息缓存的版本号。
/// </summary>
public const int McInstanceCacheVersion = 38;
public const int McInstanceCacheVersion = 39;
/// <summary>
/// Java 相关配置的版本号。
/// </summary>
public const int JavaConfigVersion = 1;
/// <summary>
/// Minecraft 安装清单的版本号。
/// </summary>
public const int McInstallManifestVersion = 1;
}
+45 -11
View File
@@ -19,6 +19,13 @@ namespace PCLCS;
/// <para/> 若为 null,则还需要调用 <see cref="CheckAsync"/> 以获取版本号。
/// </summary>
public Version? Version = version;
/// <summary>Java 的默认字符编码。</summary>
[JsonIgnore] public string? FileEncodingName;
/// <summary>
/// Java 根据系统环境确定的本地字符编码,仅 Java 17+ 可用。
/// <para/>若系统开启了 UTF-8,部分 Java 会错误地返回 JDK,因此不能使用 <see cref="Encoding.Default"/>。
/// </summary>
[JsonIgnore] public string? NativeEncodingName;
/// <summary>
/// 检查该 Java 是否存在问题,并获取其版本号。
@@ -31,7 +38,7 @@ namespace PCLCS;
if (!FileUtils.Exists(JavaExePath)) throw new FileNotFoundException("未找到 java.exe 文件", JavaExePath);
// 运行 -version
output = (await TaskUtils.RunProgramAsync(JavaExePath, "-version", 15000, cancellationToken: cancellationToken).NoCapture()).Output.Lower();
output = (await TaskUtils.RunProgramAsync(JavaExePath, "-XshowSettings:properties -version", 15000, cancellationToken: cancellationToken).NoCapture()).Output.Lower();
if (output == "") throw new InvalidOperationException("尝试运行该 Java 失败");
Logger.Trace($"Java 检查输出:{JavaExePath}{Environment.NewLine}{output}");
if (output.Contains("/lib/ext exists")) throw new InvalidOperationException("无法运行该 Java,请在删除 Java 文件夹中的 /lib/ext 文件夹后再试");
@@ -46,7 +53,16 @@ namespace PCLCS;
Version = new Version(segments.Take(4).Join("."));
if (Version.Major is <= 4 or >= 100) throw new InvalidOperationException($"分析详细信息失败,获取的版本为 {Version}{(output.Length < 500 ? $"\n原始输出为:\n{output}" : "")}");
// 获取 Java 的相关属性
if (!output.Contains("64-bit")) throw new InvalidOperationException("该 Java 为 32 位版本,请安装 64 位的 Java");
string? GetProperty(string name) {
string prefix = name + " = ";
string? line = output.Split('\n').Select(l => l.Trim()).FirstOrDefault(l => l.StartsWithF(prefix));
return line?[prefix.Length..].Trim();
}
FileEncodingName = GetProperty("file.encoding");
NativeEncodingName = GetProperty("native.encoding") ?? FileEncodingName; // 旧版本没有 native.encoding
Logger.Info($"检查 Java 成功:{this}");
available = true;
} catch (Exception ex) {
@@ -59,15 +75,21 @@ namespace PCLCS;
}
return available!.Value;
}
public void InvalidateChecked() {
isChecked = 0;
available = null;
}
[JsonIgnore] private int isChecked = 0;
/// <summary>若已进行过检查,表示该检查是否成功。若未检查,则为 null。</summary>
[JsonIgnore] internal bool? available = null;
// 相同检查
public bool Equals(Java? other)
=> other is not null && string.Equals(Folder, other.Folder, StringComparison.OrdinalIgnoreCase);
=> other is not null && PathUtils.IsSame(Folder, other.Folder);
public override bool Equals(object? obj) => obj is Java other && Equals(other);
public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Folder);
public override int GetHashCode() => StringComparer.CurrentCultureIgnoreCase.GetHashCode(PathUtils.ForCompare(Folder));
/// <summary>用户友好的描述文本。</summary>
public override string ToString()
@@ -95,8 +117,8 @@ public static class JavaUtils {
javas = await JavaUtils.CheckAllAsync(javas, cancellationToken, progress).NoCapture();
javas.Sort((left, right) => {
// 优先使用带完整 Java 的文件夹列表中的 Java
var isLeftInCandidateFolder = JavaUtils.CandidateFolders.Any(folder => PathUtils.IsParentOf(folder, left.Folder));
var isRightInCandidateFolder = JavaUtils.CandidateFolders.Any(folder => PathUtils.IsParentOf(folder, right.Folder));
var isLeftInCandidateFolder = JavaUtils.CandidateFolders.Value.Any(folder => PathUtils.IsParentOf(folder, left.Folder));
var isRightInCandidateFolder = JavaUtils.CandidateFolders.Value.Any(folder => PathUtils.IsParentOf(folder, right.Folder));
if (isLeftInCandidateFolder != isRightInCandidateFolder) return isLeftInCandidateFolder ? -1 : 1;
// 其次优先使用主版本号接近 21 的 Java
var leftDistance = left.Version is null ? int.MaxValue : Math.Abs(left.Version.Major - 21);
@@ -111,7 +133,8 @@ public static class JavaUtils {
/// <summary>
/// 存在完整 Java 的文件夹列表。
/// </summary>
internal static readonly List<string> CandidateFolders = [
internal static readonly Lazy<List<string>> CandidateFolders = new(() => {
List<string> folders = [
Paths.AppData + @".minecraft\runtime\", // 这也是 PCL 下载 Java 的路径
Paths.AppData + @".hmcl\java\",
Paths.AppData + @"ATLauncher\runtimes\minecraft\",
@@ -134,13 +157,23 @@ public static class JavaUtils {
..(Environment.GetEnvironmentVariable("JDK_HOME") + ";" + Environment.GetEnvironmentVariable("JAVA_HOME")).
Split(";", true).Select(path => path.Trim(' ', '"'))
];
return folders.Select(f => {
try {
return PathUtils.ForCompare(f);
} catch (Exception ex) {
Logger.Warn(ex, $"Java 候选文件夹路径无效({f})");
return null;
}
}).Where(f => f is not null).Cast<string>().ToList();
});
/// <summary>
/// 从常见文件夹和环境变量中搜索 Java,并将搜索结果写入设置。
/// </summary>
public static async Task RefreshListAsync(CancellationToken c = default, ProgressProvider? p = null) {
// 搜索
List<Java> javaList = await SearchFoldersAsync(true, CandidateFolders, c, p?.SplitTo(0.5)).NoCapture();
List<Java> javaList = await SearchFoldersAsync(true, CandidateFolders.Value, c, p?.SplitTo(0.5)).NoCapture();
// 将新发现的 Java 排在最前,原有列表保留既有顺序排在后面
var oldJavaList = Configs.JavaList.Get()!;
oldJavaList = await JavaUtils.CheckAllAsync(oldJavaList, c, p?.SplitTo(0.9)).NoCapture(); // 检查原有列表中的 Java
@@ -169,15 +202,16 @@ public static class JavaUtils {
string targetFolder = PathUtils.AddSlashSuffix(PathUtils.ForCompare(PathUtils.RemoveLastPart(file)));
// 判断文件夹是否包含重解析点(例如符号链接)
static bool HasReparsePoint(FileSystemInfo info) {
if (PathUtils.IsParentOf(Paths.AppData + @".minecraft\runtime\", info.FullName)) return false; // 不筛查 PCL 的下载路径(#8928)
do {
if (info.Attributes.HasFlag(FileAttributes.ReparsePoint)) return true;
if (info.Attributes.HasFlagF(FileAttributes.ReparsePoint)) return true;
info = info is FileInfo fileInfo ? fileInfo.Directory : ((DirectoryInfo) info).Parent;
} while (info is not null);
return false;
}
// 结果
var hasReparsePoint = HasReparsePoint(FileUtils.GetInfo(Path.Combine(targetFolder, "java.exe")));
var isSpecialPath = targetFolder.Contains("java8path_target_") || targetFolder.Contains("javapath_target_") || targetFolder.Contains("javatmp") || targetFolder.Contains("system32");
var isSpecialPath = targetFolder.ContainsIgnoreCase("java8path_target_") || targetFolder.ContainsIgnoreCase("javapath_target_") || targetFolder.ContainsIgnoreCase("javatmp") || targetFolder.ContainsIgnoreCase("system32");
results.Add((targetFolder, hasReparsePoint, isSpecialPath));
}
} catch (UnauthorizedAccessException) {
@@ -194,14 +228,14 @@ public static class JavaUtils {
if (results.All(r => r.HasReparsePoint)) {
Logger.Warn("找到的所有 Java 都包含重解析点,保留全部结果");
} else if (results.Any(r => r.HasReparsePoint)) {
Logger.Info($"找到以下的 Java 包含重解析点,将它们从列表中排除:{results.Where(r => r.HasReparsePoint).Select(r => r.Folder).Join('、')}");
Logger.Warn($"找到的以下 Java 包含重解析点,将它们从列表中排除:{results.Where(r => r.HasReparsePoint).Select(r => r.Folder).Join('、')}");
results = [..results.Where(r => !r.HasReparsePoint)];
}
// 筛除特殊路径
if (results.All(r => r.IsSpecialPath)) {
Logger.Warn("找到的所有 Java 都包含特殊路径,保留全部结果");
} else if (results.Any(r => r.IsSpecialPath)) {
Logger.Info($"找到以下的 Java 包含特殊路径,将它们从列表中排除:{results.Where(r => r.IsSpecialPath).Select(r => r.Folder).Join('、')}");
Logger.Warn($"找到的以下 Java 包含特殊路径,将它们从列表中排除:{results.Where(r => r.IsSpecialPath).Select(r => r.Folder).Join('、')}");
results = [..results.Where(r => !r.IsSpecialPath)];
}
+30
View File
@@ -0,0 +1,30 @@
namespace PCLCS;
public static class LaunchUtils {
/// <summary>
/// 释放补丁文件并返回完整文件路径。
/// </summary>
/// <param name="patchFilePath">补丁文件路径
/// <param name="outputDirectory">补丁文件的释放目录。</param>
public static string ExtractPatch(string patchFilePath, string outputDirectory) {
Logger.Info($"选定的资源 {patchFilePath} 输出路径:{outputDirectory}");
lock (ExtractPatchLock) { // 避免 OptiFine 和 Forge 安装时同时释放导致冲突
try {
FileUtils.ExtractResources(patchFilePath, Path.Combine(outputDirectory, PathUtils.GetLastPart(patchFilePath)), typeof(LaunchUtils));
} catch (Exception ex) {
if (!FileUtils.Exists(outputDirectory)) throw new FileNotFoundException($"释放 {patchFilePath} 失败", ex);
Logger.Warn(ex, $"{patchFilePath} 文件重新释放失败,将尝试更换文件名重新生成");
patchFilePath = Path.Combine(outputDirectory, $"{PathUtils.GetFileNameWithoutExtension(patchFilePath)}2.{PathUtils.GetExtension(patchFilePath)}");
try {
FileUtils.ExtractResources(patchFilePath, Path.Combine(outputDirectory, PathUtils.GetLastPart(patchFilePath)), typeof(LaunchUtils));
} catch (Exception ex2) {
throw new FileNotFoundException($"释放 {patchFilePath} 最终尝试失败", ex2);
}
}
}
return Path.Combine(outputDirectory, PathUtils.GetLastPart(patchFilePath));
}
private static readonly object ExtractPatchLock = new();
}
+47
View File
@@ -0,0 +1,47 @@
using System.Globalization;
namespace PCLCS;
public static class McUtils {
/// <summary>
/// 比较两个版本名;左侧较新返回正数,相同返回 0,右侧较新返回负数。
/// 无法比较两个预发布版的大小。
/// 支持的格式:未知版本, 26.1-snapshot-1,1.13.2, 1.7.10-pre4, 1.8_pre, 1.14 Pre-Release 2, 1.14.4 C6
/// </summary>
public static int CompareVersion(string left, string right) {
if (left == right) return 0;
if (left == "未知版本" || right == "未知版本") return left == "未知版本" ? 1 : -1;
left = left.Lower();
right = right.Lower();
var leftParts = left.Replace("快照", "snapshot").Replace("预览版", "pre").RegexSearch("[a-z]+|[0-9]+").ToList();
var rightParts = right.Replace("快照", "snapshot").Replace("预览版", "pre").RegexSearch("[a-z]+|[0-9]+").ToList();
for (int i = 0; ; i++) {
if (i >= leftParts.Count && i >= rightParts.Count) return string.Compare(left, right, StringComparison.Ordinal); // 两边均缺失,感觉是一个东西
string leftValue = i < leftParts.Count ? leftParts[i] : "0";
string rightValue = i < rightParts.Count ? rightParts[i] : "0";
if (leftValue == rightValue) continue;
int leftNumber = leftValue switch {
"rc" => -1,
"pre" => -2,
"snapshot" => -3,
"experimental" => -4,
_ => int.TryParse(leftValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out int leftResult) ? leftResult : 0
};
int rightNumber = rightValue switch {
"rc" => -1,
"pre" => -2,
"snapshot" => -3,
"experimental" => -4,
_ => int.TryParse(rightValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out int rightResult) ? rightResult : 0
};
if (leftNumber == 0 && rightNumber == 0) return string.Compare(leftValue, rightValue, StringComparison.Ordinal);
if (leftNumber != rightNumber) return leftNumber.CompareTo(rightNumber);
}
}
/// <summary>
/// 比较两个版本名;左侧不旧于右侧返回 True。
/// </summary>
public static bool CompareVersionGE(string left, string right) => CompareVersion(left, right) >= 0;
}
+71
View File
@@ -0,0 +1,71 @@
namespace PCLCS;
/// <summary>
/// MC 版本安装清单及 PCL 管理文件清单。
/// </summary>
/// <param name="SchemaVersion"> 清单格式版本;无版本的旧清单为 0。</param>
/// <param name="Components"> 安装目标及受管理组件版本。</param>
/// <param name="LastModifiedUtc"> 最后修改时间(UTC)。</param>
/// <param name="ManagedFiles"> 受管理文件;没有文件时为空列表。</param>
public record McInstallManifest(int SchemaVersion, McComponents Components, DateTime LastModifiedUtc, IReadOnlyList<McInstallManifest.ManagedFile> ManagedFiles) {
/// <summary>
/// 清理以 Mod 形式安装的、已删除或修改的组件的记录,并更新清单。
/// </summary>
public McInstallManifest RefreshModComponents(string gameFolder) {
List<ManagedFile> invalidFiles = [];
foreach (ManagedFile entry in ManagedFiles) {
if (entry.Component is null) continue; // 不检查非组件文件
FileInfo file = new(Path.Combine(gameFolder, entry.RelativePath));
if (!file.Exists || file.LastWriteTimeUtc != entry.LastWriteTimeUtc.ToUniversalTime()) invalidFiles.Add(entry);
}
if (!invalidFiles.Any()) return this;
return this with {
Components = Components with {
OptiFineEntry = invalidFiles.Any(file => file.Component == "OptiFine") ? null : Components.OptiFineEntry,
FabricApiVersion = invalidFiles.Any(file => file.Component == "FabricApi") ? null : Components.FabricApiVersion,
OptiFabricVersion = invalidFiles.Any(file => file.Component == "OptiFabric") ? null : Components.OptiFabricVersion
},
ManagedFiles = ManagedFiles.Where(file => !invalidFiles.Contains(file)).ToList(),
LastModifiedUtc = DateTime.UtcNow
};
}
/// <summary>
/// 由 PCL 管理并记录的版本内文件。
/// </summary>
/// <param name="RelativePath"> 相对于游戏目录的文件路径。确保路径为全小写且可比较。</param>
/// <param name="Sha1"> 小写的 SHA1。</param>
/// <param name="LastWriteTimeUtc"> 目标文件的最后修改时间(UTC)。</param>
/// <param name="Component"> 安装器组件名;为空时表示文件由整合包管理。</param>
public record ManagedFile(string RelativePath, string Sha1, DateTime LastWriteTimeUtc,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? Component = null);
}
/// <param name="MinecraftVersion"> 目标 Minecraft 版本。</param>
/// <param name="ModLoaderVersions"> 目标中各个 Mod Loader 的版本;原版为空字典。</param>
/// <param name="OptiFineEntry"> OptiFine 条目信息,不安装时为 <see langword="null"/>。</param>
/// <param name="FabricApiVersion"> 由 PCL 管理的 Fabric API 版本;不安装时为 <see langword="null"/>。</param>
/// <param name="OptiFabricVersion"> 由 PCL 管理的 OptiFabric 版本;不安装时为 <see langword="null"/>。</param>
[method: JsonConstructor]
public record McComponents(string MinecraftVersion, Dictionary<ModLoaders, string> ModLoaderVersions,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] JObject? OptiFineEntry = null,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? FabricApiVersion = null,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? OptiFabricVersion = null) {
[JsonIgnore]
public string? OptiFineVersion => OptiFineEntry?["InstanceName"]?.Value<string>()?.AfterLast("-OptiFine_");
public McComponents(string minecraftVersion, JObject? optiFineEntry, string? forgeVersion,
string? neoForgeVersion, string? fabricVersion, string? fabricApiVersion, string? optiFabricVersion, string? liteLoaderVersion)
: this(minecraftVersion, [], optiFineEntry, fabricApiVersion, optiFabricVersion) {
if (forgeVersion is not null) ModLoaderVersions[ModLoaders.Forge] = forgeVersion;
if (neoForgeVersion is not null) ModLoaderVersions[ModLoaders.NeoForge] = neoForgeVersion;
if (fabricVersion is not null) ModLoaderVersions[ModLoaders.Fabric] = fabricVersion;
if (liteLoaderVersion is not null) ModLoaderVersions[ModLoaders.LiteLoader] = liteLoaderVersion;
}
public bool IsEquivalentTo(McComponents other) =>
MinecraftVersion == other.MinecraftVersion &&
OptiFineVersion == other.OptiFineVersion &&
FabricApiVersion == other.FabricApiVersion &&
OptiFabricVersion == other.OptiFabricVersion &&
ModLoaderVersions.Count == other.ModLoaderVersions.Count &&
ModLoaderVersions.All(entry => other.ModLoaderVersions.TryGetValue(entry.Key, out string? version) && version == entry.Value);
}
+295
View File
@@ -0,0 +1,295 @@
using System.Globalization;
namespace PCLCS;
/// <summary>
/// Minecraft 版本号和组件信息
/// </summary>
public sealed class McVersion {
/// <summary>
/// 原版版本名,如 26.1、26.1-snapshot-1、1.12.2、16w01a。
/// </summary>
public string VanillaName {
get => _vanillaName;
set {
_vanillaName = value ?? "Unknown";
Vanilla = NameToVersion(_vanillaName);
}
}
private string _vanillaName = "Unknown";
/// <summary>
/// 可比较的三段式原版版本号。
/// 旧格式会将次版本号移到主版本号,例如 1.20.1 会转换为 20.0.1,若没有版本号,例如旧快照,则为 9999.0.0。
/// </summary>
public Version Vanilla = new(9999, 0, 0);
/// <summary> 原版版本号是否可靠(不是通过猜测获取)。</summary>
public bool Reliable;
/// <summary> 原版版本号是否有效。</summary>
public bool Valid => Vanilla.Major < 1000;
/// <summary> 可供比较的原版 Drop 序数,例如 26.3.2 为 263,1.21.5 为 210;若没有版本号,例如旧快照,则直接指定为 209。</summary>
public int Drop => Valid ? Vanilla.Major * 10 + Vanilla.Minor : 209;
/// <summary> 已安装的 Mod Loader,可能有多个。未安装时为 <see cref="ModLoaders.None"/>。</summary>
public ModLoaders ModLoaders => ModLoaderVersions.Keys.Aggregate(ModLoaders.None, (result, loader) => result | loader);
/// <summary> 已安装的各个 Mod Loader 及其版本;无法确定精确版本时为“未知版本”。</summary>
public Dictionary<ModLoaders, string> ModLoaderVersions { get; } = [];
/// <summary> OptiFine 版本名,如 C8、C9_pre10;未识别到时为 null。</summary>
public string? OptiFine;
/// <summary> OptiFine 是否以 Mod 形式安装。</summary>
public bool OptiFineAsMod;
/// <summary> 版本的发布时间。</summary>
public DateTime ReleaseTime = new(1970, 1, 1, 15, 0, 0);
/// <summary> 是否为愚人节版本。</summary>
public bool IsFool;
/// <summary> 是否为快照、预发布版、发布候选或实验性版本。</summary>
public bool IsSnapshot;
/// <summary> 是否识别到已安装的 OptiFine。</summary>
public bool HasOptiFine => OptiFine is not null;
/// <summary>
/// 可供比较的 OptiFine 版本序数。
/// </summary>
public int OptiFineCode {
get {
if (OptiFine is null or "未知版本" || OptiFine.Length == 0) return 0;
// 字母编号,如 G2 中的 G(7)
int result = char.ToUpperInvariant(OptiFine[0]) - 'A' + 1;
// 末尾数字,如 C5 beta4 中的 5
result = result * 100 + (int.TryParse(OptiFine.Substring(1).RegexSeek(@"\d+"), out int mainVersion) ? mainVersion : 0);
// 测试标记(正式版为 99,Pre[x] 为 50+x,Beta[x] 为 x)
result *= 100;
string? preview = OptiFine.RegexSeek(@"(?i)(pre|beta)\d*");
if (preview is null) return result + 99;
int sequence = int.TryParse(preview.RegexSeek(@"\d+"), out int value) ? value : 1; // 为 pre 或 beta 结尾,视作 1
return result + (preview.StartsWithF("pre", true) ? 50 : 0) + sequence;
}
}
/// <summary>
/// 可供比较的类 Forge 版本序数。
/// </summary>
public int ForgelikeCode {
get {
if (!ModLoaderVersions.TryGetValue(ModLoaders.Forge, out string? version)) {
ModLoaderVersions.TryGetValue(ModLoaders.NeoForge, out version);
}
if (version is null or "未知版本") return 0;
// 提取所有数字段,以兼容 beta、alpha 和 snapshot 等非数字后缀。
int[] segments = version.RegexSearch(@"\d+")
.Select(segment => int.TryParse(segment, out int value) ? value : 0).ToArray();
return segments.Length switch {
>= 4 => segments[0] * 1000000 + segments[1] * 10000 + segments[3],
3 => segments[0] * 1000000 + segments[1] * 10000 + segments[2],
2 => segments[0] * 1000000 + segments[1] * 10000,
1 => segments[0] * 1000000,
_ => 0
};
}
}
/// <summary>
/// 创建默认 Minecraft 版本。
/// </summary>
/// <param name="vanillaName">原版版本名。</param>
/// <param name="reliable">版本名是否来自可靠依据。</param>
public McVersion(string vanillaName, bool reliable = true) {
Reliable = reliable;
VanillaName = vanillaName;
IsFool = IsFoolName(VanillaName);
IsSnapshot = IsSnapshotName(VanillaName);
}
/// <summary>
/// 通过 Components 创建 Minecraft 版本信息。
/// </summary>
public McVersion(McComponents components) : this(components.MinecraftVersion) {
OptiFine = components.OptiFineVersion?.AfterLast("HD_").AfterFirst("_");
foreach (var loader in components.ModLoaderVersions) ModLoaderVersions[loader.Key] = loader.Value;
}
/// <summary>
/// 通过 JSON 识别 Minecraft 版本和已安装组件。
/// </summary>
/// <param name="instanceName">实例文件夹名。</param>
/// <param name="json">展开的版本 JSON。</param>
/// <param name="jarVersionJson">版本 JAR 中的 version.json。</param>
public McVersion(string instanceName, JObject json, JObject? jarVersionJson = null) {
string jsonText = json.ToString(Formatting.None);
DateTime releaseTime = new(1970, 1, 1, 15, 0, 0); // 未知版本也可能显示为 1970 年
try {
if (json["releaseTime"] is JToken releaseTimeToken) releaseTime = releaseTimeToken.ToObject<DateTime>();
} catch {
}
bool reliable = true;
// 获取游戏版本
string vanillaName = (GetVanillaName(instanceName, json, jarVersionJson, releaseTime, ref reliable) ?? "Unknown").Replace("_unobfuscated", "").Replace(" Unobfuscated", "");
Reliable = reliable;
VanillaName = vanillaName;
ReleaseTime = releaseTime;
// 确定版本分类
string jsonType = json.Value<string>("type") ?? "";
// OptiFine
OptiFine = jsonText.ContainsIgnoreCase("optifine") ? jsonText.RegexSeek(@"(?<=HD_U_)[^\"":/]+") ?? "未知版本": null;
// 愚人节与快照版本
DateTime fixedReleaseTime = releaseTime.ToUniversalTime().AddHours(2);
IsFool = jsonType == "fool" || IsFoolName(vanillaName) || (fixedReleaseTime.Month == 4 && fixedReleaseTime.Day == 1 && jsonType == "snapshot");
IsSnapshot = IsSnapshotName(vanillaName) || instanceName.ContainsIgnoreCase("combat") || jsonType is "snapshot" or "pending" or "fool";
if (!Reliable) Logger.Info($"无法完全确认 MC 版本号的版本:{VanillaName}");
// LiteLoader
if (jsonText.ContainsIgnoreCase("liteloader")) {
ModLoaderVersions[ModLoaders.LiteLoader] = jsonText.RegexSeek(@"(?<=com\.mumfrey:liteloader:)[^\"":/]+") ?? "未知版本";
}
// Fabric、Forge、NeoForge
if (jsonText.ContainsIgnoreCase("net.fabricmc:fabric-loader") || jsonText.ContainsIgnoreCase("org.quiltmc:quilt-loader")) {
ModLoaderVersions[ModLoaders.Fabric] = jsonText.RegexSeek(@"(?<=(net\.fabricmc:fabric-loader:|org\.quiltmc:quilt-loader:))[0-9\.]+(\+build.[0-9]+)?")?
.Replace("+build", "") ?? "未知版本";
} else if (jsonText.ContainsIgnoreCase("net.neoforge")) {
// 1.20.1 JSON 范例:"--fml.forgeVersion", "47.1.99"
// 1.20.2+ JSON 范例:"--fml.neoForgeVersion", "20.6.119-beta"
ModLoaderVersions[ModLoaders.NeoForge] = FindArgumentValue(json, "--fml.neoForgeVersion") ?? FindArgumentValue(json, "--fml.forgeVersion") ?? "未知版本";
} else if (jsonText.ContainsIgnoreCase("minecraftforge")) {
ModLoaderVersions[ModLoaders.Forge] = jsonText.RegexSeek(@"(?<=forge:[0-9\.]+(?:_pre[0-9]*)?-)[0-9\.]+")
?? jsonText.RegexSeek(@"(?<=net\.minecraftforge:minecraftforge:)[0-9\.]+")
?? jsonText.RegexSeek(@"(?<=net\.minecraftforge:fmlloader:[0-9\.]+-)[0-9\.]+") ?? "未知版本";
}
}
private static string? GetVanillaName(string instanceName, JObject json, JObject? jarVersionJson, DateTime releaseTime, ref bool reliable) {
// 获取发布时间并判断是否为老版本
if (releaseTime.Year is > 2000 and < 2013) return "Old";
// 实验性快照
if (json.Value<string>("type") == "pending") return "pending";
// 从 PCL 下载的版本信息中获取版本号
if (json["clientVersion"] is JToken clientVersion) return clientVersion.ToString();
// 从 HMCL 下载的版本信息中获取版本号
if (json["patches"] is JArray patches) {
JToken? gameVersion = patches.OfType<JObject>().FirstOrDefault(patch => patch.Value<string>("id") == "game")?["version"];
if (gameVersion is not null) return gameVersion.ToString();
}
// 从 Forge / NeoForge Arguments 中获取版本号
if (FindArgumentValue(json, "--fml.mcVersion") is { Length: > 0 } fmlVersion) return fmlVersion;
// 从继承版本中获取版本号
string inherit = json["inheritsFrom"]?.ToString() ?? "";
if (inherit.Length > 0) {
string jar = json["jar"]?.ToString() ?? ""; // LiteLoader 优先使用 Jar
return jar.Length > 0 ? jar : inherit;
}
// 从下载地址中获取版本号
string? download = json["downloads"]?.ToString(Formatting.None).RegexSeek(@"(?<=launcher.mojang.com/mc/game/)[^/]*");
if (download is not null) return download;
// 从 Forge/OptiFine/Fabric 版本中获取版本号
string librariesText = json["libraries"]!.ToString();
string[] libraryPatterns = [
@"(?<=net.minecraftforge:forge:)[0-9]{1,2}.[0-9+.]+",
@"(?<=net.minecraftforge:fmlloader:)[0-9]{1,2}.[0-9+.]+",
@"(?<=optifine:OptiFine:)[0-9]{1,2}.[0-9+.]+",
@"(?<=((fabricmc)|(quiltmc)):intermediary:)[^\""]*"
];
foreach (string pattern in libraryPatterns) {
if (librariesText.RegexSeek(pattern) is string match) return match;
}
// 从 jar 项中获取版本号
if (json["jar"] is JToken jarToken) return jarToken.ToString();
// 从 jar 文件的 version.json 中获取版本号
if (jarVersionJson?["name"] is JToken jarNameToken && jarNameToken.ToString() is { Length: < 32 } jarName) { // 因为 wiki 说这玩意儿可能是个 hash,虽然我没发现
Logger.Info($"从版本 jar 中的 version.json 获取到版本号:{jarName}");
return jarName;
}
string VersionPattern = @"(([1-9][0-9]w[0-9]{2}[a-g])|((1|[2-9][0-9])\.[0-9]+(\.[0-9]+)?(-(pre|rc|snapshot-?)[1-9]*| Pre-Release( [1-9])?)?))(_unobfuscated)?";
// 从 JSON 的 ID 中获取
if (json["id"]!.ToString().RegexSeek(VersionPattern, RegexOptions.IgnoreCase) is string idMatch) return idMatch;
// 非准确的版本判断警告
Logger.Info($"无法完全确认 MC 版本号的版本:{instanceName}");
reliable = false;
// 从文件夹名中获取
if (instanceName.RegexSeek(VersionPattern, RegexOptions.IgnoreCase) is string folderMatch) return folderMatch;
// 从 JSON 出现的版本号中获取
JObject withoutLibraries = (JObject) json.DeepClone();
withoutLibraries.Remove("libraries");
return withoutLibraries.ToString(Formatting.None).RegexSeek(VersionPattern, RegexOptions.IgnoreCase);
}
private static bool IsFoolName(string name) {
name = name.Lower();
return name.StartsWithF("2.0") || name is "15w14a" or "1.rv-pre1" or
"3d shareware v1.34" or "20w14∞" or "22w13oneblockatatime" or "23w13a_or_b" or
"24w14potato" or "25w14craftmine" or "26w14a" || name.StartsWithF("20w14inf");
}
private static bool IsSnapshotName(string name) =>
new[] { "w", "snapshot", "rc", "pre", "experimental", "-" }.Any(name.ContainsIgnoreCase);
/// <summary>
/// 读取 arguments 中指定参数的字符串值。
/// </summary>
private static string? FindArgumentValue(JObject json, string name) {
JArray? arguments = json["arguments"]?["game"] as JArray;
if (arguments is null) return null;
for (int i = 0; i + 1 < arguments.Count; i++) {
if (arguments[i]?.ToString() == name) return arguments[i + 1]?.ToString();
}
return null;
}
/// <summary>
/// 判断版本字符串是否符合 Minecraft 原版格式,例如 1.x、26.x。
/// </summary>
public static bool IsStandardFormat(string? version) {
if (version is null) return false;
if (Regex.IsMatch(version, @"^1\.\d")) return true;
return double.TryParse(version.RegexSeek(@"^[2-9]\d\.\d+"), NumberStyles.Float,
CultureInfo.InvariantCulture, out double value) && value >= 26;
}
/// <summary>
/// 将版本名转换为 Drop 序数;无法转换时返回 209。
/// </summary>
public static int VersionToDrop(string? vanillaName) {
if (vanillaName is null) return 209;
Version version = NameToVersion(vanillaName);
return version.Major >= 1000 ? 209 : version.Major * 10 + version.Minor;
}
/// <summary>
/// 将 Drop 序数转换为版本字符串。
/// </summary>
public static string DropToVersion(int drop) {
if (drop >= 250) return $"{drop / 10}.{drop % 10}";
if (drop != 209) return $"1.{drop / 10}";
Logger.Warn(new Exception("尝试将旧快照版 Drop 序数转换为版本字符串,这不应该发生!"));
return "1.20";
}
/// <summary>
/// 将版本名转换为可比较的三段式原版版本号。
/// 对老版本格式,例如 1.20.3,会被转换为 20.0.3。
/// 对愚人节版本,返回其基于的正式版版本号。
/// 若没有版本号,例如旧快照,则为 9999.0.0。
/// </summary>
public static Version NameToVersion(string? name) {
if (name is null) return new Version(9999, 0, 0);
name = name.Lower().Replace("_unobfuscated", "").Replace(" unobfuscated", "");
// 愚人节版本
if (name.StartsWithF("2.0")) return NameToVersion("1.5.1");
if (name == "15w14a") return NameToVersion("1.8.3");
if (name.ContainsIgnoreCase(".rv-pre")) return NameToVersion("1.9.2");
if (name.ContainsIgnoreCase("shareware")) return NameToVersion("1.13.2");
if (name.StartsWithF("20w14") && name != "20w14a") return NameToVersion("1.15.2");
if (name.ContainsIgnoreCase("oneblockatatime")) return NameToVersion("1.18.2");
if (name.ContainsIgnoreCase("23w13a") && name != "20w13a") return NameToVersion("1.19.4");
if (name == "24w14potato") return NameToVersion("1.20.4");
if (name == "25w14craftmine") return NameToVersion("1.21.4");
if (name == "26w14a") return NameToVersion("26.1.1");
// 其余版本
static int ParseSegment(string[] segments, int index) {
if (index >= segments.Length) return 0;
// 只读取该段开头连续的数字,忽略其后的预览版标记。
return int.TryParse(segments[index].RegexSeek(@"^\d+"), out int value) ? value : 0;
}
string[] segments = name.Split([' ', '_', '-', '.'], StringSplitOptions.None);
if (name.StartsWithF("1.")) {
return new Version(ParseSegment(segments, 1), 0, ParseSegment(segments, 2));
} else if (name.RegexCheck(@"^[2-9][0-9]\.")) {
return new Version(ParseSegment(segments, 0), ParseSegment(segments, 1), ParseSegment(segments, 2));
} else {
return new Version(9999, 0, 0);
}
}
}
+423
View File
@@ -0,0 +1,423 @@
{
"f3a3c9d792bdb48595405d52c02cc7baf1db4ecb": "OptiFine_1.7.2_HD_U_E3.jar",
"657169c421ce280f80c73b05ab71ad582d2b37fd": "OptiFine_1.7.2_HD_U_E4.jar",
"99f20d449912cd860b409e4679c54dd8da43d55e": "OptiFine_1.7.2_HD_U_E7.jar",
"8253f6ed418a2a61a612cc9e69fd1fea1d11fa7d": "OptiFine_1.7.2_HD_U_E8.jar",
"379e52fc02cd75562d201b0c505269b6109b2136": "OptiFine_1.7.2_HD_U_F3.jar",
"fb04b91e47b5eaa172d2b76c843e488dba8f909b": "OptiFine_1.7.2_HD_U_F7.jar",
"b4b6c2c99f5734e4267030a47f2eaad2796eb2a7": "OptiFine_1.7.10_HD_U_D3.jar",
"a0b533bc97b24fe771f5b08536c6339d04f92bae": "OptiFine_1.7.10_HD_U_D4.jar",
"5e9327dd05a1e762c23c3c0227445d90f193922f": "OptiFine_1.7.10_HD_U_D6.jar",
"7a7d2adf21038d241dd375faa7316fb2c74d34d3": "OptiFine_1.7.10_HD_U_D7.jar",
"2345677be44dd56a0ae5b90e747a590c3cc00ff8": "OptiFine_1.7.10_HD_U_D8.jar",
"23f133173428aee712eb11f200b461e3114bde31": "OptiFine_1.7.10_HD_U_E3.jar",
"2130d7266224da6a7325051e37444f7e7c19277c": "OptiFine_1.7.10_HD_U_E7.jar",
"55c9c475ecd0f62cc1776ce52be04c5937112936": "OptiFine_1.8.0_HD_U_H5.jar",
"bb2c2997cdc66cfb3c311a3dc065752a4845c1a8": "OptiFine_1.8.0_HD_U_H6.jar",
"692b0b7e878c3416f21b4347fab4516f3cc86dfc": "OptiFine_1.8.0_HD_U_H7.jar",
"5464962b66b9b0dacb3d787e6a71d2365ae50bca": "OptiFine_1.8.0_HD_U_H8.jar",
"b7718b03111bec04036fd3352ce374eea273171c": "OptiFine_1.8.0_HD_U_I3.jar",
"0d1dcf64b4d104a71e8f65a1362a163ed81a9030": "OptiFine_1.8.0_HD_U_I7.jar",
"e5e5eb03748af0fa160b502a28359c0144e254d2": "OptiFine_1.8.8_HD_U_H5.jar",
"9294bc4562a143c35ece03a6b3d3a000109def5f": "OptiFine_1.8.8_HD_U_H6.jar",
"1c06c011904ab22952a96f73d4b575a5c265d207": "OptiFine_1.8.8_HD_U_H7.jar",
"3984e4dd222bb62cb7e28e4d3092029cc7534fd1": "OptiFine_1.8.8_HD_U_H8.jar",
"0dc33a9761ceac19ca0bbb1ea6813c0160d43aa1": "OptiFine_1.8.8_HD_U_I3.jar",
"30cbf4d61052cbfb736a78117da333df23c129a3": "OptiFine_1.8.8_HD_U_I7.jar",
"d8240cfa7387b994fdf2a64bdc8a8860857adc18": "OptiFine_1.8.9_HD_U_H5.jar",
"2636689b31810896cf80493f6fc4d8c1783048ea": "OptiFine_1.8.9_HD_U_H6.jar",
"e4544c9210bc056abffa1986a33c4e72623d4f31": "OptiFine_1.8.9_HD_U_H7.jar",
"4b73738913453bcbca969d3e77f05ae0cf5b8a7e": "OptiFine_1.8.9_HD_U_H8.jar",
"d5cd8b902077075bbf1d865cfd5b3362a6863ace": "OptiFine_1.8.9_HD_U_I3.jar",
"58bec8f8766918dcad2b8412f1fbb6f30cc9319f": "OptiFine_1.8.9_HD_U_I7.jar",
"dcab4849a24ccad5a6a3496c67bc5a88411d2a35": "OptiFine_1.8.9_HD_U_L5.jar",
"d362d58a28f5373b141b9e426e8e160638bfafcd": "OptiFine_1.8.9_HD_U_M5.jar",
"1f9dcc9735e94ebb5195c4014300ea462eb12878": "preview_OptiFine_1.8.9_HD_U_L6_pre1.jar",
"6e0b3b86e51c2647c98e277475ee7e68f32a0d0e": "preview_OptiFine_1.8.9_HD_U_M5_pre2.jar",
"7287fcd1a415ab87eca5e89457cc3507aa3fa579": "preview_OptiFine_1.8.9_HD_U_M6_pre1.jar",
"0bb792be16ec78d8b62586cc4856950befb3391b": "preview_OptiFine_1.8.9_HD_U_M6_pre2.jar",
"fd87c53197312614c34669feb7608f74a30da712": "OptiFine_1.9.0_HD_U_B1.jar",
"f99941037f3633926ffebb3eda67f296a701c019": "OptiFine_1.9.0_HD_U_B2.jar",
"34c47106cda1371ecf13e61aed4c1b53a3a53163": "OptiFine_1.9.0_HD_U_B3.jar",
"ebc7258e13344097588c73a0b48457083069e1df": "OptiFine_1.9.0_HD_U_B5.jar",
"999df92e8904f9268e549135edbec322d332ef62": "OptiFine_1.9.0_HD_U_D7.jar",
"0fe031e95f9c01481c9376d9a1df68f9e9857db8": "OptiFine_1.9.0_HD_U_D8.jar",
"f4bc2eb5e039e842eb07181ea564eba487513228": "OptiFine_1.9.0_HD_U_E3.jar",
"78870a94ec3c4db8aebc4c29d4d39bdda2bf05da": "OptiFine_1.9.0_HD_U_E7.jar",
"d3d55bb37e76d1932cb3ad9dde4b9127993f856d": "OptiFine_1.9.0_HD_U_H5.jar",
"b5faec3b84f42dd396512a3ce7c1b57c0b972b70": "OptiFine_1.9.0_HD_U_I5.jar",
"92742ecac143b1d28c215ca145bd9775a01c5085": "preview_OptiFine_1.9.0_HD_U_I5_pre1.jar",
"c87029938376017deba9108322bcb8424d64e92c": "OptiFine_1.9.2_HD_U_B1.jar",
"d3e5e1fa480e66603ca037d74966a95d25968d51": "OptiFine_1.9.2_HD_U_B2.jar",
"fbd545f921d098235a28f87cda55a0504e831954": "OptiFine_1.9.2_HD_U_B3.jar",
"1ae8e6daeeccb6ee48618e8ccdc0b6276d47944f": "OptiFine_1.9.2_HD_U_D7.jar",
"3350c829b29d170e9ac19809a3ae45cb099277b5": "OptiFine_1.9.2_HD_U_D8.jar",
"c032b2ae0b6087077efef8ae89b612e0d4ea81b7": "OptiFine_1.9.2_HD_U_E3.jar",
"3d8cbc19bcad9e9ecc98f607e10f65673a09b482": "OptiFine_1.9.4_HD_U_B4.jar",
"b6b9e66b34ddcda5294b016602fb17d741ff6f54": "OptiFine_1.9.4_HD_U_B5.jar",
"060ddd5a0b7b7ac5586de04ae53cf923ae5c7290": "OptiFine_1.9.4_HD_U_B6.jar",
"2c39611a89dc583cdb403f402c2efd2bd8c6da63": "OptiFine_1.9.4_HD_U_D7.jar",
"4b26926208de270225eb9d3af24a4ff3841238f5": "OptiFine_1.9.4_HD_U_D8.jar",
"012f82fce41aee5e6e56e551d450a07b2b361646": "OptiFine_1.9.4_HD_U_E3.jar",
"cf9c075fa71dab34cdf1c376a76bd08295c69ce6": "OptiFine_1.9.4_HD_U_E7.jar",
"b3a76b92e0e9594f6fa135f1156b9cc13712db20": "OptiFine_1.9.4_HD_U_H5.jar",
"68a206db0d425dd85f6eba33e52122b01e844c46": "OptiFine_1.9.4_HD_U_I5.jar",
"2e48d20605340cfad844a6587d3848e328fd4399": "preview_OptiFine_1.9.4_HD_U_I5_pre1.jar",
"d7a1e5f40e28fa3283b82bf3af7d3f433b91df08": "OptiFine_1.10_HD_U_B6.jar",
"717a02b21136e022cdb4d7d6ec6a4125f4ed2312": "OptiFine_1.10_HD_U_B7.jar",
"0b5d1719592a57cf414643c5b56f4bb3c23a5b94": "OptiFine_1.10_HD_U_C1.jar",
"236747126edcf0c97fc6a20fa7f6491accbf0108": "OptiFine_1.10_HD_U_D7.jar",
"c5fd78710e821f06f871994032e951065c15c40d": "OptiFine_1.10_HD_U_D8.jar",
"1bea8b0bae04ca4b25c6824a6f23a5442d5aa926": "OptiFine_1.10_HD_U_E3.jar",
"7a7195348ea583458b657573b5fcc72e4adc8308": "OptiFine_1.10_HD_U_E7.jar",
"3bcc46f718711cd6fe3db49de4cc810b75420de8": "OptiFine_1.10_HD_U_H5.jar",
"a0070afe9c1c0ced4bcfcda85359e07917d569bd": "OptiFine_1.10_HD_U_I5.jar",
"f9a36eb0246ad6ba303e670082be2c2515c50dcd": "preview_OptiFine_1.10_HD_U_I5_pre1.jar",
"3ea5dae651b6d64900562d1cde5070ebd9db2936": "OptiFine_1.10.2_HD_U_C1.jar",
"41968442e8d1e30e9a32e7a5594caca22312d9d6": "OptiFine_1.10.2_HD_U_C2.jar",
"9c8bc96d3125199e96f5dbfda685ea5dd92c253e": "OptiFine_1.10.2_HD_U_C3.jar",
"7ec4ccc213b482936baaffb9f1937f7147796893": "OptiFine_1.10.2_HD_U_D1.jar",
"3ee5aecc8e11567fb46de0b94e23cf1428f4c9fc": "OptiFine_1.10.2_HD_U_D2.jar",
"98059ed7e93d0fc058203569ccfe965b022fb18f": "OptiFine_1.10.2_HD_U_D3.jar",
"c3165d0a0207d72e18f4533e7aa5ca30e96819a9": "OptiFine_1.10.2_HD_U_D4.jar",
"347359aef422d64711b470f4cf0803cb072da0fa": "OptiFine_1.10.2_HD_U_D6.jar",
"4ddf7c45e00839031f5793451541bca002f9257f": "OptiFine_1.10.2_HD_U_D7.jar",
"5c3685e279c664a818c41ca213b2bc9bfc4340b0": "OptiFine_1.10.2_HD_U_D8.jar",
"129581af3d7d73b506b42d822cb2d3fce68c8e2e": "OptiFine_1.10.2_HD_U_E3.jar",
"f052d269109154d851cc7a8e766c78802a76e9b2": "OptiFine_1.10.2_HD_U_E7.jar",
"f285cfe6753c5d987e4cb0940da05e9513334d98": "OptiFine_1.10.2_HD_U_H5.jar",
"12fd8dd33150f9e4436f23bac08265305f949e20": "OptiFine_1.10.2_HD_U_I5.jar",
"adac69b25bb95242422a635965c7f17503c694be": "preview_OptiFine_1.10.2_HD_U_I5_pre1.jar",
"06031e4c828b66facefdfa32cd29c585909d9e45": "OptiFine_1.11_HD_U_B1.jar",
"449aef14cee17cf1c79d4bad73b69107202d3c1c": "OptiFine_1.11_HD_U_B2.jar",
"7663e1ee572c742e96a71a355f0775baf7cbc6ef": "OptiFine_1.11_HD_U_B3.jar",
"bde9d34f84a030734c7b9d09eda700c7c828eb96": "OptiFine_1.11_HD_U_B5.jar",
"579cfb75b86f3404c5f6e8f694c51b09b7c9b261": "OptiFine_1.11_HD_U_B6.jar",
"350af22b31dbc27d1b928e7144582fd361deb193": "OptiFine_1.11_HD_U_B7.jar",
"236161183cc5cd5faf84dd27824cc3eaa107ed5f": "OptiFine_1.11_HD_U_B8.jar",
"9ca32d42c7e5ad9de9665ffee9ddd41dddd33b66": "OptiFine_1.11_HD_U_C3.jar",
"2e45de9e9fb385f24c6eecb1a64537de78778105": "OptiFine_1.11_HD_U_C7.jar",
"32df58fec7148e75b599b18691f80e69b7f9cd1b": "OptiFine_1.11_HD_U_F5.jar",
"71057cb7a3d0971c3b56e1fbbc333304a72e1352": "OptiFine_1.11_HD_U_G5.jar",
"c2ee1e1019ab26bee56447f4df8a2639d2029925": "preview_OptiFine_1.11_HD_U_G5_pre1.jar",
"083a002215e2174c0d40af20d2f3844ea349a354": "OptiFine_1.11.2_HD_U_B5.jar",
"eb92ce00048faa95b184d121fa667074532fb9db": "OptiFine_1.11.2_HD_U_B6.jar",
"7449a2db44e86303d588b526dc73127c16b0e94f": "OptiFine_1.11.2_HD_U_B7.jar",
"3e1006c935fe27d83459e77d336460ac5b5c81b4": "OptiFine_1.11.2_HD_U_B8.jar",
"049e7e134e8c7364ab61810d84289394e17921de": "OptiFine_1.11.2_HD_U_B9.jar",
"4871f2edf7ded30c374aa770e9ea4adcc0b786d0": "OptiFine_1.11.2_HD_U_C1.jar",
"e7a54d9e203a1b951f8373ab6dcdedb348e536b1": "OptiFine_1.11.2_HD_U_C2.jar",
"8d7fd0facc2299f3dad9dc7c19cc13f72a780343": "OptiFine_1.11.2_HD_U_C3.jar",
"20db26eaafb1c4ebd99e1dc2fffe4a62456bf60e": "OptiFine_1.11.2_HD_U_C7.jar",
"e4efcf05086389fe18d6fe62eb8ab7512a07323c": "OptiFine_1.11.2_HD_U_F5.jar",
"65c425ab628d1748b489dee8619271402428509e": "OptiFine_1.11.2_HD_U_G5.jar",
"f253807a4689e44da030acd5c2187d2184885e87": "preview_OptiFine_1.11.2_HD_U_G5_pre1.jar",
"1fdacab6f3b0df1f46142acf9c427db2a653d680": "OptiFine_1.12_HD_U_C2.jar",
"a62a02c8437a0dccaf2ea89ab8392fd40dc92eb1": "OptiFine_1.12_HD_U_C3.jar",
"36d6f6f64048f68dd248a4d3c43a6f5e51fbb9c5": "OptiFine_1.12_HD_U_C4.jar",
"fd8e0e3ba43568e5ca0ce31c87373806d269b7e5": "OptiFine_1.12_HD_U_C5.jar",
"25ba634e5105e6d1e615dad0046905b68313cc9f": "OptiFine_1.12_HD_U_C7.jar",
"b9cd2775fc49bf009f82d3ce36235b1e702d46bb": "OptiFine_1.12_HD_U_F5.jar",
"3b8369d33c3257cb247c22b13a41b7db0924e948": "OptiFine_1.12_HD_U_G5.jar",
"aeee885ccf821c34d86537184389873d8c312ae1": "preview_OptiFine_1.12_HD_U_G5_pre1.jar",
"c754771bf6a7371a7084459505c244cbec61a7cf": "OptiFine_1.12.1_HD_U_C5.jar",
"4362a9d38542b5f4ba07782fc4d07949f4fede08": "OptiFine_1.12.1_HD_U_C6.jar",
"38785968beeb796cde55f91995c6cf8bfbc3b062": "OptiFine_1.12.1_HD_U_C7.jar",
"e6f9e6b573119c052062192695fed4ef388db9c1": "OptiFine_1.12.1_HD_U_F5.jar",
"3c9cd74810cad75de5091633a1e0c7d3d76ea09c": "OptiFine_1.12.1_HD_U_G5.jar",
"aaaaff3ae13a0cdffa2475632cf6303b9f86e0b0": "preview_OptiFine_1.12.1_HD_U_G5_pre1.jar",
"a9d21f084a485bc12fce8c9bea1660c6d4b08855": "OptiFine_1.12.2_HD_U_C5.jar",
"2159a0a754828d63446fca7b6f8fa57b710c6048": "OptiFine_1.12.2_HD_U_C6.jar",
"8ed8009fc82b43909190882927f335d5250a0e19": "OptiFine_1.12.2_HD_U_C7.jar",
"a55768a83db4ad0599f68d9bf3fa8041e2ae3852": "OptiFine_1.12.2_HD_U_C8.jar",
"e0236d21a2a164f2af1cfff8f9f325f53c9125f2": "OptiFine_1.12.2_HD_U_C9.jar",
"b91a0d82c24eef95be0af92993b14e94b842f166": "OptiFine_1.12.2_HD_U_D1.jar",
"04c28238506dc3561addb6efb87c9878d5b82ffe": "OptiFine_1.12.2_HD_U_D2.jar",
"b784000951d41f54d67685a0a275d9f61f810514": "OptiFine_1.12.2_HD_U_D3.jar",
"343f814410401ebdbb3314deb17c27cbab67bfac": "OptiFine_1.12.2_HD_U_E1.jar",
"5c87706b6d14061fed248506d8e3f95b7824ad18": "OptiFine_1.12.2_HD_U_E2.jar",
"e805d4be5c2a3343488c573145606e90bb13816d": "OptiFine_1.12.2_HD_U_E3.jar",
"0e8d45666e80829c9ab74527628dace9187fbfb5": "OptiFine_1.12.2_HD_U_F4.jar",
"84ada0f48bcfb16c37c49283627597bb74d935f6": "OptiFine_1.12.2_HD_U_F5.jar",
"ca3aea3a09ce215906c346fe190907fe0347b0c4": "OptiFine_1.12.2_HD_U_G5.jar",
"23a8be042f354ec97c99672906c3e61fdf54ca2c": "preview_OptiFine_1.12.2_HD_U_F6_pre1.jar",
"b71f993a364c7945081cc6cde9a20d29b4e43f1f": "preview_OptiFine_1.12.2_HD_U_G5_pre1.jar",
"ab2118b1ed23444d6decf081e0295e4c679af6c2": "preview_OptiFine_1.12.2_HD_U_G6_pre1.jar",
"25498de48dd9b7ed2ba5b83464950ad132e61c1f": "preview_OptiFine_1.14_HD_U_F1_pre6.jar",
"396ce7f0e22aae3543134baabd61ad547334c0aa": "OptiFine_1.14.2_HD_U_F1.jar",
"c8c6da3e2ae32d020d9001d4489aad702680f50f": "preview_OptiFine_1.14.2_HD_U_F1_pre15.jar",
"52420ef34128a8b15d7c8f5ed8f7ebc94b708efd": "OptiFine_1.14.3_HD_U_F1.jar",
"887d648f26f7ba17ea34c0c2dcff4096f7a226b1": "OptiFine_1.14.3_HD_U_F2.jar",
"ad8a8f60931f48e2873238d1460268bb4111f6c9": "OptiFine_1.14.4_HD_U_F2.jar",
"c9710081fb7012ddb56838ac54653ac2258a2404": "OptiFine_1.14.4_HD_U_F3.jar",
"c09f4d1ad66df8e3039334023422f1bc8cc211b8": "OptiFine_1.14.4_HD_U_F4.jar",
"3408258990309de773cc5a8ab71cb4137718a82b": "OptiFine_1.14.4_HD_U_F5.jar",
"117c01a5431244a8b6a3ac393374dac9624f9be6": "OptiFine_1.14.4_HD_U_G5.jar",
"ac602fe6a39d2fb4cadfe08e87af0b0daa2d8ac9": "preview_OptiFine_1.14.4_HD_U_G5_pre1.jar",
"630a32be9b711b5a479507159d5d2598189fed33": "OptiFine_1.15.2_HD_U_G6.jar",
"8339858b33056ec8975f6c0c9e1694a11f0d209a": "preview_OptiFine_1.15.2_HD_U_G1_pre1.jar",
"6cdf96d972b5f70c4575ff875a031c4931985686": "preview_OptiFine_1.15.2_HD_U_G1_pre10.jar",
"489d2284f2d50bd7c7cf4c216a766503d37e1f3c": "preview_OptiFine_1.15.2_HD_U_G1_pre12.jar",
"c66d412c4cb46c6f2c76c224dc45a62d30ee8b43": "preview_OptiFine_1.15.2_HD_U_G1_pre13.jar",
"582eb1d6470cc133db563bd1c2c8bc2068215806": "preview_OptiFine_1.15.2_HD_U_G1_pre18.jar",
"64d738862683c54a22e1856b2c13379599ca79ce": "preview_OptiFine_1.15.2_HD_U_G1_pre19.jar",
"3da9d466c847c50a73dc634dabc6a57f3b24e2f7": "preview_OptiFine_1.15.2_HD_U_G1_pre2.jar",
"e6583246a85dba26a478dd4925c298bb5b3f277a": "preview_OptiFine_1.15.2_HD_U_G1_pre20.jar",
"84c5d50e26aaa02733fa15a8c13ab845fbdea97f": "preview_OptiFine_1.15.2_HD_U_G1_pre21.jar",
"c55e449386eeca34df5b428d85927242fc841438": "preview_OptiFine_1.15.2_HD_U_G1_pre22.jar",
"459e17c77059671ef5daa9ab794400e8ae25095d": "preview_OptiFine_1.15.2_HD_U_G1_pre23.jar",
"2c6e4f61b1ceea92b76e0be40bc672e5b6d58efe": "preview_OptiFine_1.15.2_HD_U_G1_pre24.jar",
"4ea53e3d202239c5ffa2affa809e36998194f1bc": "preview_OptiFine_1.15.2_HD_U_G1_pre26.jar",
"29861ba50021b08200ef33f77c776f9f5b7135f7": "preview_OptiFine_1.15.2_HD_U_G1_pre27.jar",
"454dc0e03dab9d30a2ccce5823b7a0b309f5c859": "preview_OptiFine_1.15.2_HD_U_G1_pre28.jar",
"3c3623d75dc47722149edfb93fdca121c32e8987": "preview_OptiFine_1.15.2_HD_U_G1_pre29.jar",
"942e23fa0169c693fb1b645fae4db9217c6b5916": "preview_OptiFine_1.15.2_HD_U_G1_pre3.jar",
"bdbd92e5252c0afc0f22d6d5840f96762e5d01ea": "preview_OptiFine_1.15.2_HD_U_G1_pre30.jar",
"5daa9461c5762bdd647665f49d2e0ef0eb348910": "preview_OptiFine_1.15.2_HD_U_G1_pre4.jar",
"0029ab50050d19f4009f0a7d708982dde736f022": "preview_OptiFine_1.15.2_HD_U_G1_pre5.jar",
"646b279927cab6036e8f295efd5bda268acd85cf": "preview_OptiFine_1.15.2_HD_U_G1_pre7.jar",
"f11e79a7bc91a5fd985cb756445fc88e8c2dd31b": "preview_OptiFine_1.15.2_HD_U_G1_pre9.jar",
"1e9869b63a89a8ab81a58e1473dd2c2291fbd943": "preview_OptiFine_1.15.2_HD_U_G6_pre5.jar",
"87533b8ac0dfeb11241041f197f8f95c0ffa8c08": "OptiFine_1.16.1_HD_U_G2.jar",
"3e610c9bd89ae2a54cc06de6dfb1b00cd45e9895": "preview_OptiFine_1.16.1_HD_U_G2_pre1.jar",
"df817e1ab0e577be1dfb6c20a9b628d3d725c780": "preview_OptiFine_1.16.1_HD_U_G2_pre10.jar",
"dbca331e7abbe64d6aa360e6a93beec2935d24e3": "preview_OptiFine_1.16.1_HD_U_G2_pre11.jar",
"6fef880338cabe32443557762ca43fb36fe01f7c": "preview_OptiFine_1.16.1_HD_U_G2_pre12.jar",
"ea7bea6511f3b20aa672544d28dbc6d1a2ee8b41": "preview_OptiFine_1.16.1_HD_U_G2_pre13.jar",
"efdc788f59e1c1bcde3b64430322d8990ce7c7d6": "preview_OptiFine_1.16.1_HD_U_G2_pre14.jar",
"388100110b4dc8ef28eaa2f3a4ae2f33a8beb5ae": "preview_OptiFine_1.16.1_HD_U_G2_pre15.jar",
"63f5f097ce2e678d69767981b09b133ab8eac4f5": "preview_OptiFine_1.16.1_HD_U_G2_pre16.jar",
"c56031c3f6e5dcad536115e9f6a1fb5710b72391": "preview_OptiFine_1.16.1_HD_U_G2_pre2.jar",
"da269cdb109b6441ebed179a8d9f974590006cbb": "preview_OptiFine_1.16.1_HD_U_G2_pre3.jar",
"ad9b5d4e0bdd5697d4f6a6b9c30a64977bb8834e": "preview_OptiFine_1.16.1_HD_U_G2_pre4.jar",
"f6d5ea6cedff1f80bed298f9b456de2448b1f268": "preview_OptiFine_1.16.1_HD_U_G2_pre5.jar",
"f57a4269d404d4025de4d864b88510504e785e19": "preview_OptiFine_1.16.1_HD_U_G2_pre6.jar",
"28b46592e2f510a246a8bf62a2b12e808094167c": "preview_OptiFine_1.16.1_HD_U_G2_pre7.jar",
"26207daec6acc66250116e4f73295c9a031b491a": "preview_OptiFine_1.16.1_HD_U_G2_pre8.jar",
"6902e902866b765ff8e31716980c2067d491e004": "preview_OptiFine_1.16.1_HD_U_G2_pre9.jar",
"dde4274c696063496323731ad7b58e0f6d7265af": "OptiFine_1.16.2_HD_U_G3.jar",
"5b5627a814e8c95aa7ac27fa4cf2cc0b8bd4519b": "OptiFine_1.16.2_HD_U_G5.jar",
"5dafc10061020c519ba4abe4982a7dc80eae30f6": "preview_OptiFine_1.16.2_HD_U_G3_pre1.jar",
"0c0d9828984da34f9d5967027c244bb2c83a4396": "preview_OptiFine_1.16.2_HD_U_G3_pre2.jar",
"7c791e4348808132b8c7b59e6350ae2ee25a1918": "preview_OptiFine_1.16.2_HD_U_G3_pre3.jar",
"9a30a2cbc81095d38b4dc17e7d30a4b84e10e235": "preview_OptiFine_1.16.2_HD_U_G3_pre4.jar",
"ec1a4ce97e29c9ee2ac259d98e35bc386035a66d": "preview_OptiFine_1.16.2_HD_U_G3_pre5.jar",
"e03b0cdfbbcf01b1371b3e1090dee64e60a759d8": "preview_OptiFine_1.16.2_HD_U_G3_pre6.jar",
"40ae5fbb4e3724d6e5525d2688445c09ecfe5d39": "preview_OptiFine_1.16.2_HD_U_G3_pre7.jar",
"dc83d55f74722e1e6f503817e00e5ba2e6cb4abb": "OptiFine_1.16.3_HD_U_G3.jar",
"89d9472ee507946caa227e226524e5260ed944db": "OptiFine_1.16.3_HD_U_G4.jar",
"6a8f57ca84628da47b011e608ff449cde7ec13ed": "OptiFine_1.16.3_HD_U_G5.jar",
"f53bad69adaac9e523577dcb1c65b158802f3dee": "preview_OptiFine_1.16.3_HD_U_G3_pre2.jar",
"5b970eeae2ba07316b3e3a53da1d71de87c9f9f2": "preview_OptiFine_1.16.3_HD_U_G4_pre1.jar",
"599d9de6b656077b03fbebc9478924c548a6e9e0": "OptiFine_1.16.4_HD_U_G5.jar",
"7cce72db1f77c6178b549fa430e241eadb318f69": "OptiFine_1.16.4_HD_U_G6.jar",
"f49c042fa099907eafcbe22d7035d84f8b5715f1": "OptiFine_1.16.4_HD_U_G7.jar",
"f0b1eb59acdae57e10bb928046ee26012ef220ad": "preview_OptiFine_1.16.4_HD_U_G6_pre12.jar",
"ded7894eebda787b7bd5013aa8c66577f2ba03fc": "preview_OptiFine_1.16.4_HD_U_G7_pre1.jar",
"57cfcec3dd99b9f0950d0d6305bc95ecd8563212": "OptiFine_1.16.5_HD_U_G6.jar",
"706fb33784045d69e7ac5730426d813a39511dcd": "OptiFine_1.16.5_HD_U_G7.jar",
"137363842c5bfdebb72356a4079faeef3157eb3c": "OptiFine_1.16.5_HD_U_G8.jar",
"ad0f1577ce02a703185e46ee9e24fdeb65479fd5": "preview_OptiFine_1.16.5_HD_U_G7_pre10.jar",
"11770876367980da546c68bc95c4f794582907f2": "preview_OptiFine_1.16.5_HD_U_G7_pre11.jar",
"c5b32d2bad3bd54c31ddb29869a55fe44409cfe2": "preview_OptiFine_1.16.5_HD_U_G7_pre2.jar",
"9fc9cc09e72a3060d577f62f3535820056b0c2d4": "preview_OptiFine_1.16.5_HD_U_G7_pre4.jar",
"577fae32b60ac8d1b91e3fa645dc004d4a9e513e": "preview_OptiFine_1.16.5_HD_U_G7_pre5.jar",
"4bccf7f371ac83523796b4706569cc5077c46aea": "preview_OptiFine_1.16.5_HD_U_G7_pre6.jar",
"97b8d413a909354e2e0f42943cf292941dfe4c38": "preview_OptiFine_1.16.5_HD_U_G7_pre7.jar",
"9c48a804ee990e526000c7061bde9aa6f75d62a6": "preview_OptiFine_1.16.5_HD_U_G7_pre8.jar",
"9a9b69a52a4514f5d25c9f47127a822e93458fa3": "preview_OptiFine_1.16.5_HD_U_G7_pre9.jar",
"d16a450ec4205d02319480799008d8177ba05d69": "preview_OptiFine_1.16.5_HD_U_G8_pre1.jar",
"539c215aa0e8b8f696b325e89f3f9a3658ab32c8": "preview_OptiFine_1.16.5_HD_U_G8_pre10.jar",
"311ed81a27d24047b6ade33f46ac21c962897016": "preview_OptiFine_1.16.5_HD_U_G8_pre11.jar",
"4bc50f85809a299d30c3e3e73cfd87cad61cf7e6": "preview_OptiFine_1.16.5_HD_U_G8_pre12.jar",
"7de1b8e9f7cd6d7ca941d90121e50206aba80c68": "preview_OptiFine_1.16.5_HD_U_G8_pre13.jar",
"a4e021a75aab7ed1eb83ff7b45182d497aa930f2": "preview_OptiFine_1.16.5_HD_U_G8_pre14.jar",
"0ddcc4f47e10b2d671ebbf9147eb16cf8f1bb92a": "preview_OptiFine_1.16.5_HD_U_G8_pre2.jar",
"b8600be4fde6f8cc3c28173cc9bde6923956ff42": "preview_OptiFine_1.16.5_HD_U_G8_pre3.jar",
"d46b10098309929d9a6583323f059e65ba40a5e5": "preview_OptiFine_1.16.5_HD_U_G8_pre4.jar",
"e835424b36fdae3d5f46c6bf096852848127f1e7": "preview_OptiFine_1.16.5_HD_U_G8_pre5.jar",
"5a84983221331597f58e9636d41f1fb0c9e3de61": "preview_OptiFine_1.16.5_HD_U_G8_pre6.jar",
"d03ce2209d787539e5e51f80a7cbffc69a49ce9f": "preview_OptiFine_1.16.5_HD_U_G8_pre7.jar",
"1c646dc058aa32898917589858d900407883ba3f": "preview_OptiFine_1.16.5_HD_U_G8_pre8.jar",
"9bf369aa609e5612247473326dc298e6e6c9fecf": "preview_OptiFine_1.16.5_HD_U_G8_pre9.jar",
"b88b05278fbbf400cb5ec0cc26e0c5b7306e0c1e": "preview_OptiFine_1.17_HD_U_G9_pre16.jar",
"a6beac3091d17a4ba420116c75c89faeaee48539": "preview_OptiFine_1.17_HD_U_G9_pre17.jar",
"2839c5e74f64500561ee3e8605c51d9f06727c22": "preview_OptiFine_1.17_HD_U_G9_pre18.jar",
"455243ff55177189fff140ba7f4c4777cc977ef6": "preview_OptiFine_1.17_HD_U_G9_pre19.jar",
"bcc390e34bd529a86e6ae0e25d50417f46ba482f": "preview_OptiFine_1.17_HD_U_G9_pre20.jar",
"d30c23d745df54fd6ea2374e9c0b4392f0a78199": "preview_OptiFine_1.17_HD_U_G9_pre21.jar",
"075e7f8473b6785c3c6bbeeb41a3728f6ff3c3ba": "preview_OptiFine_1.17_HD_U_G9_pre22.jar",
"2ffaee36c717ae918cc93661df739740636900c9": "preview_OptiFine_1.17_HD_U_G9_pre23.jar",
"4385493e7ce4c6f4a3945f1d96246bce0b151c7e": "preview_OptiFine_1.17_HD_U_G9_pre24.jar",
"bcc33e67f134861eb30b1f0b9a829183c8afdebf": "preview_OptiFine_1.17_HD_U_G9_pre25.jar",
"6a8e34605cc828091027d9f011b804d916808af4": "preview_OptiFine_1.17_HD_U_G9_pre26.jar",
"4714bdceaefc15e0af9d5c72de8c05cf3051d760": "OptiFine_1.17.1_HD_U_G9.jar",
"cc64084e73d6423baf7ccbdd9f3760dd56f2fce5": "OptiFine_1.17.1_HD_U_H1.jar",
"1ee73bd882385a7abb9896b9469586741a6850a8": "preview_OptiFine_1.17.1_HD_U_G9_pre26.jar",
"537c1fbee9ebe25b49a8e6fb93db162d217f1c83": "preview_OptiFine_1.17.1_HD_U_G9_pre27.jar",
"68e1124f4b0376773d8d581da78f9bb6fc76486e": "preview_OptiFine_1.17.1_HD_U_G9_pre28.jar",
"3f80a562b39439831491c650c65ecb762e70d5f7": "preview_OptiFine_1.17.1_HD_U_G9_pre29.jar",
"9751c69568a61ce5b2a602b4dcf77c1cea57c709": "preview_OptiFine_1.17.1_HD_U_G9_pre30.jar",
"162fa1b4a0d5b89dad6030b5fd5903a3f24d39c7": "preview_OptiFine_1.17.1_HD_U_G9_pre31.jar",
"c4f3a33d892948927cecfaa60b284217325557c5": "preview_OptiFine_1.17.1_HD_U_G9_pre32.jar",
"12dbbc8ccd8dc02806768fb3fbe4a2d8c7c11301": "preview_OptiFine_1.17.1_HD_U_G9_pre33.jar",
"f74d1dfcb24c43e633076c63dd4492b29fca14b3": "preview_OptiFine_1.17.1_HD_U_G9_pre34.jar",
"42e55681fb106475ef13d5c5ef7113fb631392d4": "preview_OptiFine_1.17.1_HD_U_G9_pre35.jar",
"245ddb68349f5f81d067f4d9402d62db3d439d50": "preview_OptiFine_1.17.1_HD_U_H1_pre1.jar",
"0ac033f5ba4089311af55172281e73df143b9ac4": "preview_OptiFine_1.17.1_HD_U_H1_pre2.jar",
"68a3727bcdb0eac0986ff4e883d93fef406eb1c3": "preview_OptiFine_1.17.1_HD_U_H1_pre3.jar",
"a6534877558f0438bda604dc43093d61c6de88ae": "preview_OptiFine_1.17.1_HD_U_H1_pre4.jar",
"3e18db17b8695feb0a188ecdcb81b561d9d7030c": "preview_OptiFine_1.17.1_HD_U_H1_pre5.jar",
"c2e0c4391a671cd06931f7b6e965085c04c58762": "preview_OptiFine_1.17.1_HD_U_H1_pre6.jar",
"1ffb7325df5bf01b1d99bbbed8006a477b861394": "preview_OptiFine_1.17.1_HD_U_H1_pre7.jar",
"f2fa727e67b4795ddea3cc37cc5ced42e9c3b0c5": "preview_OptiFine_1.17.1_HD_U_H1_pre8.jar",
"aee167aff9937a068d514c85e3f8c766db99ed43": "preview_OptiFine_1.17.1_HD_U_H1_pre9.jar",
"59709e09632c80ab80ffcc4b5bf380d073bf467f": "preview_OptiFine_1.17.1_HD_U_H2_pre1.jar",
"52629bc5319ec203edd42de1776cb8d67a0e6b1b": "OptiFine_1.18_HD_U_H3.jar",
"e5077b4be03956118613162b5dbcf90406b27747": "preview_OptiFine_1.18_HD_U_H3_pre7.jar",
"326c160481cc849bccb821939992c101a8fe163b": "OptiFine_1.18.1_HD_U_H4.jar",
"1679654fdf49fd1216b090d51d3d8ec906094619": "OptiFine_1.18.1_HD_U_H5.jar",
"c86534ee821a78efc741c9cb407eec76625e1bab": "OptiFine_1.18.1_HD_U_H6.jar",
"34c98382131efa1394cff06528238dc44d255794": "preview_OptiFine_1.18.1_HD_U_H4_pre1.jar",
"d6cd0a2d0243ecad107220bff639eb46277762ea": "preview_OptiFine_1.18.1_HD_U_H5_pre1.jar",
"e24d766864f9238b94be5b3ad65608d7eb521fad": "preview_OptiFine_1.18.1_HD_U_H5_pre2.jar",
"bc1b87a6379acf5a5dca2a56196080ace49e4864": "preview_OptiFine_1.18.1_HD_U_H5_pre3.jar",
"555a6b8e420bd63a04d9f338d61f3333b4ccf5d9": "preview_OptiFine_1.18.1_HD_U_H5_pre4.jar",
"1d02694393f10992cbbd11a802568b0890b4dc81": "preview_OptiFine_1.18.1_HD_U_H5_pre5.jar",
"e3a22adc6931dcd7513f26afa1adecd62a35641b": "preview_OptiFine_1.18.1_HD_U_H5_pre6.jar",
"445a8cb8a1d565d8a1fabc0ba984c5936edbf561": "OptiFine_1.18.2_HD_U_H6.jar",
"8d83d4d5e325887e000a462029d2bf7a6e77eebc": "OptiFine_1.18.2_HD_U_H7.jar",
"2a461188167c1da6b1cfd520d85a656f45c74d70": "OptiFine_1.18.2_HD_U_H9.jar",
"e591777f272df533b62260a6dbe0e6cde62066f6": "preview_OptiFine_1.18.2_HD_U_H6_pre1.jar",
"f7c727c99e7f58ef9b86cc96fb894f628391016c": "preview_OptiFine_1.18.2_HD_U_H7_pre1.jar",
"cb51529f94e0eff5b9da92c8f70b1d498662a115": "preview_OptiFine_1.18.2_HD_U_H7_pre2.jar",
"ceea3c6612d2ec36c6ef1ca4a9355a141df9089f": "preview_OptiFine_1.18.2_HD_U_H7_pre3.jar",
"596c3a399889c3e2ef1d8fee994e8cf5a91e457e": "preview_OptiFine_1.18.2_HD_U_H8_pre1.jar",
"85f545aeb9d232ae970b0efa24e833f9c5d0fe33": "preview_OptiFine_1.18.2_HD_U_H9_pre1.jar",
"b9bbabb8f31f2b04ca1a1a8645df1be701f705bd": "preview_OptiFine_1.18.2_HD_U_H9_pre2.jar",
"c2bf213b4f60b6a29e402e3eaf219a4c85a5d1e6": "preview_OptiFine_1.18.2_HD_U_H9_pre3.jar",
"64c8c35313d25a64a8a8677cb851a2c83556ddc8": "OptiFine_1.19_HD_U_H8.jar",
"c668eaf9b6ce4e64f26a29fb27e830229ab4bef3": "OptiFine_1.19_HD_U_H9.jar",
"cda9130e0d36f0d6e06950ab393a8929b502a440": "preview_OptiFine_1.19_HD_U_H8_pre1.jar",
"cd1ae81f544672df776dc4426269771d2e82d0b2": "preview_OptiFine_1.19_HD_U_H8_pre10.jar",
"cb3ad2a9b4c23675ca569058f55dd174eed67a38": "preview_OptiFine_1.19_HD_U_H8_pre2.jar",
"2f7139ea0888ed18006e3a39b7475e88a3fd545d": "preview_OptiFine_1.19_HD_U_H8_pre3.jar",
"85cd70bb714e65935abf351811889a47a648bb96": "preview_OptiFine_1.19_HD_U_H8_pre4.jar",
"c65cbae4c1cfb5b9a5a573e4b83424df2d81b172": "preview_OptiFine_1.19_HD_U_H8_pre5.jar",
"10cd57890ebf449f5c258cff1c60d8dcefcc6cf6": "preview_OptiFine_1.19_HD_U_H8_pre6.jar",
"eb03e60c2fa9904a47eec1ef4a2c8804ee6cc0b8": "preview_OptiFine_1.19_HD_U_H8_pre7.jar",
"40d0b514168161fdefc91594f0d3f774f831d7de": "preview_OptiFine_1.19_HD_U_H8_pre8.jar",
"8e5bedd1c1a3641caad0743c4629db51c11f8feb": "preview_OptiFine_1.19_HD_U_H9_pre1.jar",
"7745216a810f0d617ed17a520fda44e12456492f": "preview_OptiFine_1.19_HD_U_H9_pre2.jar",
"6d6ed920eb99836e76549a7803af85881fcdee34": "preview_OptiFine_1.19_HD_U_H9_pre3.jar",
"7604e63ca74876b4e8ea9c3a36dbe80111c776b1": "preview_OptiFine_1.19_HD_U_H9_pre4.jar",
"b42dcce86c88ad4bcfa52a9d297afa800477aa78": "OptiFine_1.19.1_HD_U_H9.jar",
"648427a02940b3c53b3678cd5df1ff6997bd1e6c": "preview_OptiFine_1.19.1_HD_U_H9_pre1.jar",
"7cc221a6df6831b62c3b676b5f958e82a4487fd8": "OptiFine_1.19.2_HD_U_H9.jar",
"f304c56c7ab9119ce4269f2b019cd4668f48376d": "OptiFine_1.19.2_HD_U_I1.jar",
"708c1ecc148a0bd7d341e8f5e005e670903650f7": "OptiFine_1.19.2_HD_U_I2.jar",
"e0e924990bd12cc499fc89439f0b2c50b6e147fa": "preview_OptiFine_1.19.2_HD_U_I1_pre1.jar",
"465b229481c0a285195a168bf6581549a5cd7373": "preview_OptiFine_1.19.2_HD_U_I1_pre2.jar",
"af4398d0ce0628bcc0204e61c157e3c8951b9ad5": "preview_OptiFine_1.19.2_HD_U_I1_pre3.jar",
"35ef18f953bbea9a4cba6e14d0731d56f888114f": "preview_OptiFine_1.19.2_HD_U_I1_pre4.jar",
"b24d8725a8d06e1c190d1d2b3bc51c899f74b36d": "OptiFine_1.19.3_HD_U_I3.jar",
"f5ce615d9886e377079fd13cc4700a51d5a9c7b6": "preview_OptiFine_1.19.3_HD_U_I2_pre1.jar",
"10e6d97ea2c97cf0575a7c587fb11e1ddb73aa71": "preview_OptiFine_1.19.3_HD_U_I2_pre2.jar",
"89718cd608225d7c23f7e180d011daa4a47ce6bd": "preview_OptiFine_1.19.3_HD_U_I2_pre3.jar",
"204baf0fd59432054aebd2f83c83fe15fdd3e611": "preview_OptiFine_1.19.3_HD_U_I2_pre4.jar",
"90a52b3adc94a1b9a70f03f4c4ea14dfdf3424d8": "preview_OptiFine_1.19.3_HD_U_I2_pre5.jar",
"050d787936cc239ea8db0aa250d2ed72a44fa71d": "OptiFine_1.19.4_HD_U_I3.jar",
"253e9d29c6826c134fbdeea074d1d91c3410225c": "OptiFine_1.19.4_HD_U_I4.jar",
"068b998d6c5db5b3bb7ad6a0a7d0c6aefc4dc191": "preview_OptiFine_1.19.4_HD_U_I3_pre3.jar",
"a8b9156811c4745dbd612ebbb1df6f93d2fa734a": "preview_OptiFine_1.19.4_HD_U_I3_pre4.jar",
"6f2793e6236a27c06a74e8a1876ea6b29be038a7": "preview_OptiFine_1.19.4_HD_U_I3_pre5.jar",
"9ad82ef9f32fb5a0e7e7e0c7e8906c9f319a9705": "preview_OptiFine_1.19.4_HD_U_I3_pre6.jar",
"cf02ee8b5cf6922fbc28d6ffc8183949fff55d49": "preview_OptiFine_1.19.4_HD_U_I3_pre7.jar",
"702e0e9987209f66a5b4f825e7ac658fd49dd4b1": "preview_OptiFine_1.19.4_HD_U_I3_pre8.jar",
"04c8eaeadf24c42929500812a8f3f5fe634f0d1f": "preview_OptiFine_1.19.4_HD_U_I3_pre9.jar",
"968784e051c2c44f10a4ab448c979572977969c0": "preview_OptiFine_1.19.4_HD_U_I4_pre1.jar",
"abe4b1dbda7d95eb47953ef8aa11ed8ed1d8e238": "preview_OptiFine_1.20_HD_U_I5_pre3.jar",
"6612ce97d846018126f91cdbae23f0eff6bd2bab": "preview_OptiFine_1.20_HD_U_I5_pre4.jar",
"f37d62b6a071f6cd3d6bdfa378b56f7f5b9ec7c6": "preview_OptiFine_1.20_HD_U_I5_pre5.jar",
"c44bd276edd10fa910d929a4e7f6b8c7ffe6c23f": "OptiFine_1.20.1_HD_U_I5.jar",
"227914ab1037762eae44a2e66a568b87d7a02365": "OptiFine_1.20.1_HD_U_I6.jar",
"7484cf60bd8e019c40fa448daa294bd31f52962b": "preview_OptiFine_1.20.1_HD_U_I5_pre4.jar",
"953b1b4f15f56329bc4ade851846ef004ebbd5d0": "preview_OptiFine_1.20.1_HD_U_I5_pre5.jar",
"ebbdb3076f8291d7f8e10cbec3b1d3c49a0a701d": "preview_OptiFine_1.20.1_HD_U_I5_pre6.jar",
"b831d59eeb63122893f87a62ec3e5b3f1cc5586a": "preview_OptiFine_1.20.1_HD_U_I5_pre7.jar",
"dc93d991b07b2c5c6d2cd50a553599900c256264": "preview_OptiFine_1.20.1_HD_U_I5_pre8.jar",
"040a73427541170bccf162c04f4129b3909b8da1": "preview_OptiFine_1.20.1_HD_U_I5_pre9.jar",
"9f5fd75ede7770ad35383443b0a203f026cef5db": "preview_OptiFine_1.20.1_HD_U_I6_pre1.jar",
"85d5566b440036b926c5dd306108e48e7fb9b6e8": "preview_OptiFine_1.20.1_HD_U_I6_pre2.jar",
"d09c110358117336472414720bf961a581690dc5": "preview_OptiFine_1.20.1_HD_U_I6_pre3.jar",
"3365577f5161f9a1bbfefb15675bb5ece27600e4": "preview_OptiFine_1.20.1_HD_U_I6_pre4.jar",
"f317c701ae8c533fc34809dbce914bcced4e9a03": "preview_OptiFine_1.20.1_HD_U_I6_pre5.jar",
"445b32b14a2c2802d5a0832a708712d2fabafbc2": "preview_OptiFine_1.20.1_HD_U_I6_pre6.jar",
"9ea6b1389e88b81797f54b9d8d1e870d3fe8532c": "preview_OptiFine_1.20.2_HD_U_I7_pre1.jar",
"f2475e8d5746477d1d2972cd5aae62a5d9c04264": "OptiFine_1.20.4_HD_U_I7.jar",
"fec28a2fa152744bc2b47b3c5db3217974aa57ee": "preview_OptiFine_1.20.4_HD_U_I7_pre2.jar",
"4068365a57f4b154f4ebbc670894dc9038cf3819": "preview_OptiFine_1.20.4_HD_U_I7_pre3.jar",
"2481e0bed7610bfdfe26dea5a6bd8f01537c4ea8": "preview_OptiFine_1.20.4_HD_U_I7_pre4.jar",
"150990ba068cfb55bbf9e525c5ab84db0b8903e1": "preview_OptiFine_1.20.4_HD_U_I7_pre5.jar",
"fcf0f0f278d1901e4341d5ec8022bede03572430": "preview_OptiFine_1.20.4_HD_U_I7_pre6.jar",
"1c3395efa928758505b808291162a2514523b399": "preview_OptiFine_1.20.4_HD_U_I7_pre7.jar",
"4bcfec24e5d917ee96e0de6ef095bfc08782217b": "preview_OptiFine_1.20.4_HD_U_I8_pre1.jar",
"c931df21309800f1d1ece59b318e6b3e39c98176": "preview_OptiFine_1.20.4_HD_U_I8_pre2.jar",
"83f5ad147536405d86bf4ee47a7f7970e1fa8329": "preview_OptiFine_1.20.4_HD_U_I8_pre3.jar",
"7c6669319db18c9622d896da2a061479be88acf5": "preview_OptiFine_1.20.4_HD_U_I8_pre4.jar",
"bda7bbdd5e2d8c73148372e8c3b34d7c2485a4e8": "preview_OptiFine_1.20.6_HD_U_J1_pre18.jar",
"24a20f949a719480523669669a4e5e7339df6186": "OptiFine_1.21.1_HD_U_J1.jar",
"6bc74af8f733864539404a3f087aeb6f0cadc6b1": "preview_OptiFine_1.21.1_HD_U_J1_pre10.jar",
"4813a6cde6b0f5b20223d486ba7adf0816236a77": "preview_OptiFine_1.21.1_HD_U_J1_pre11.jar",
"c99ea98d8537f99b7470f1bcfa81c7ce405fd97e": "preview_OptiFine_1.21.1_HD_U_J1_pre12.jar",
"f2f55e6d61a4b90bdaa31eb520f5af78f8a1833b": "preview_OptiFine_1.21.1_HD_U_J1_pre13.jar",
"53a15b75ea9d20b644b0d3e9c7280ba172ade053": "preview_OptiFine_1.21.1_HD_U_J1_pre14.jar",
"da5575e1138fc2a4c28779cfc5e7d260253ccbdb": "preview_OptiFine_1.21.1_HD_U_J1_pre15.jar",
"0930876341beadf7014f57e9ebdd44be445dacbe": "OptiFine_1.21.3_HD_U_J2.jar",
"350f63ec3e59d21227f803d4078883884e482660": "preview_OptiFine_1.21.3_HD_U_J2_pre10.jar",
"2c48a4210acd25082c220f9601f17d1cc7769098": "preview_OptiFine_1.21.3_HD_U_J2_pre11.jar",
"8f0a097b1f7c9d065396bac9230eab62412a8b54": "preview_OptiFine_1.21.3_HD_U_J2_pre12.jar",
"f97df5638fcda7f4380d9284b329569d4e1554aa": "preview_OptiFine_1.21.3_HD_U_J2_pre8.jar",
"77df50552deea51687592b60c6d24561ee11aa74": "preview_OptiFine_1.21.3_HD_U_J2_pre9.jar",
"bcc6a7a140320566a985b1499a59fcb085255d14": "OptiFine_1.21.4_HD_U_J3.jar",
"e7bff4575b7311e11d7111506e855b4ad910481f": "preview_OptiFine_1.21.4_HD_U_J3_pre10.jar",
"52b2b6defdb8d1c189afb42125079f1b76ff72e2": "preview_OptiFine_1.21.4_HD_U_J3_pre11.jar",
"708f70aa5f70919a40d02ba75fc30d2d60c8e7cd": "preview_OptiFine_1.21.4_HD_U_J3_pre12.jar",
"86b796325d03d4571809a155c54cf6cba7c7482b": "preview_OptiFine_1.21.4_HD_U_J3_pre13.jar",
"618719a6a69eb674ea50c82cffe3d4e3c2f67abf": "preview_OptiFine_1.21.4_HD_U_J3_pre14.jar",
"98dd84b0581fcf7a27aa96238acb615b5b719825": "preview_OptiFine_1.21.4_HD_U_J3_pre15.jar",
"5c8573ae921082a069a9e7269500178f0c580c97": "preview_OptiFine_1.21.4_HD_U_J3_pre4.jar",
"17d51e1bfc94dde95368ab91aa0bb56e89c706bf": "preview_OptiFine_1.21.4_HD_U_J3_pre5.jar",
"1a4311606ac94d682800714793ea887c3276b18d": "preview_OptiFine_1.21.4_HD_U_J3_pre6.jar",
"12bf1a1b5607b5c62405a8c6ab4e1bd02bef2c8d": "preview_OptiFine_1.21.4_HD_U_J3_pre7.jar",
"f1f56403dc575fd975a000bfb133060285cacd6f": "preview_OptiFine_1.21.4_HD_U_J3_pre8.jar",
"9584de0e8204f5c855b60741d58af967dfc599a8": "preview_OptiFine_1.21.4_HD_U_J3_pre9.jar",
"0b97631e166ace9346744de33b7772df7bf5fbeb": "preview_OptiFine_1.21.4_HD_U_J4_pre1.jar",
"a776ec6c072529d4b731e0e6929a8aee56d04e76": "preview_OptiFine_1.21.4_HD_U_J4_pre2.jar",
"2a3fdfcbfc9c572b22c7d15c6129b8ea58ea1712": "preview_OptiFine_1.21.8_HD_U_J6_pre13.jar",
"ba7ca56c9b33d2fc7f7d22e20ce2765e4d257299": "preview_OptiFine_1.21.8_HD_U_J6_pre14.jar",
"097d3acd315b0682a4565ab9cbb4cdc1cc17d81f": "preview_OptiFine_1.21.8_HD_U_J6_pre15.jar",
"d7effca0de34c9442f06776848679a684f1f166a": "preview_OptiFine_1.21.8_HD_U_J6_pre16.jar",
"696ed52cdfd0ac2eafab8e98575d146b400de530": "preview_OptiFine_1.21.10_HD_U_J7_pre10.jar",
"be7bc81f0c9ce15316f76a059b0a690f62c03a5d": "preview_OptiFine_1.21.10_HD_U_J7_pre11.jar",
"731e698abe94bbd90cda628f80b4ce6ac5af8188": "preview_OptiFine_1.21.10_HD_U_J7_pre6.jar",
"63f189a4b7ff5b53e5bc56391f5274eab8555def": "preview_OptiFine_1.21.10_HD_U_J7_pre7.jar",
"7ccfc0085ccdef3f4ec21918a059ddd05171711a": "preview_OptiFine_1.21.10_HD_U_J7_pre8.jar",
"aad53ad3346944a12ed088ffbba98cf9eccc474c": "preview_OptiFine_1.21.10_HD_U_J7_pre9.jar",
"2800efeb56565f23494d372e7edac29333113e11": "OptiFine_1.21.11_HD_U_J8.jar",
"cee35b83c75da33dc07ffbb344386cae5cdc5c5b": "OptiFine_1.21.11_HD_U_J9.jar",
"687046282c370e690ec4b4aa6d90a6b42ada3ee1": "preview_OptiFine_1.21.11_HD_U_J8_pre10.jar",
"c09b05637610072bc33023d1c901e2726b7d16ce": "preview_OptiFine_1.21.11_HD_U_J8_pre11.jar",
"88682b40f08f462c4daae8e6b25ea99b1ed5cd14": "preview_OptiFine_1.21.11_HD_U_J8_pre12.jar",
"1c69de725c249d487df676470c71a7c05ece943c": "preview_OptiFine_1.21.11_HD_U_J8_pre3.jar",
"921b68e014539d75b77a47ca2117fc9e09ace4ed": "preview_OptiFine_1.21.11_HD_U_J8_pre4.jar",
"2fee496a35eeebf39fbb43017b12d752f5c37dee": "preview_OptiFine_1.21.11_HD_U_J8_pre5.jar",
"e5b5da810d43c02dccedfd99f83ae8ad63e49966": "preview_OptiFine_1.21.11_HD_U_J8_pre6.jar",
"8309638bab4d99258354b4d13c3cd82ff7c9645b": "preview_OptiFine_1.21.11_HD_U_J8_pre7.jar",
"b3d208ec7b5015842fcc77b3399b04b24e0b54cb": "preview_OptiFine_1.21.11_HD_U_J8_pre8.jar",
"c4ac7401bb5f668e4e92121501d1537cdbe7343c": "preview_OptiFine_1.21.11_HD_U_J8_pre9.jar",
"fd96031b5fc4c41ad71581eea53eb122022fff5b": "preview_OptiFine_1.21.11_HD_U_J9_pre1.jar",
"a159a217aba02cd7897394a51bd346c3b658b9f9": "preview_OptiFine_1.21.11_HD_U_J9_pre2.jar",
"2a784437076b539ea1a3c1a37ec98ff21bc3952c": "preview_OptiFine_1.21.11_HD_U_J9_pre3.jar",
"ee32ce4b406e290a0e99685afd09dc44cad03ddd": "preview_OptiFine_1.21.11_HD_U_J9_pre4.jar"
}
+4 -1
View File
@@ -27,6 +27,7 @@
<Using Include="System.Collections.Generic" />
<Using Include="System.Collections.Concurrent" />
<Using Include="System.Text" />
<Using Include="System.Text.RegularExpressions" />
<Using Include="System.Net" />
<Using Include="System.Runtime.CompilerServices" />
<Using Include="System.Runtime.InteropServices" />
@@ -55,8 +56,10 @@
<!-- 内嵌的资源 -->
<ItemGroup>
<None Remove="Resource\WikiEntries.txt" />
<EmbeddedResource Include="Launch\JavaWrapper.jar" LogicalName="Launch\JavaWrapper.jar" />
<EmbeddedResource Include="Launch\LwjglUnsafeAgent.jar" LogicalName="Launch\LwjglUnsafeAgent.jar" />
<EmbeddedResource Include="Resource\WikiEntries.txt" LogicalName="Resource\WikiEntries.txt" />
<EmbeddedResource Include="Minecraft\OptiFineHashes.json" LogicalName="Minecraft\OptiFineHashes.json" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualBasic" />
-2
View File
@@ -9,7 +9,6 @@ public static class Resource {
1 => ModLoaders.Forge,
3 => ModLoaders.LiteLoader,
4 => ModLoaders.Fabric,
5 => ModLoaders.Quilt,
6 => ModLoaders.NeoForge,
_ => ModLoaders.None,
};
@@ -24,7 +23,6 @@ public static class Resource {
ModLoaders.Forge => 1,
ModLoaders.LiteLoader => 3,
ModLoaders.Fabric => 4,
ModLoaders.Quilt => 5,
ModLoaders.NeoForge => 6,
_ => 0,
};
File diff suppressed because it is too large. Load diff
+10 -22
View File
@@ -1,6 +1,5 @@
Imports System.Reflection
Imports System.Windows.Threading
Imports Microsoft.Win32
Public Class Application
@@ -10,7 +9,10 @@ Public Class Application
'核心初始化
MeloongCore.Main.Init("PCL")
MeloongCore.Wpf.Main.Init()
If Not (e.Args.Contains("--update") OrElse e.Args.Contains("--gpu") OrElse e.Args.Contains("--memory")) Then '若是通过命令行调用,则不检查单例、不轮换日志(#9137)
WaitingMutex(e)
Logger.Instance = New PclLogger With {.logFolder = Paths.BaseThenName, .MinLevel = If(ModeDebug, LogLevel.Trace, LogLevel.Info)}
End If
'提升主线程优先级
Thread.CurrentThread.Priority = ThreadPriority.Highest
'执行开发版测试
@@ -23,9 +25,7 @@ Public Class Application
End If
'检查 .NET Framework 版本
Try
Using ndpKey As RegistryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\")
If ndpKey?.GetValue("Release") IsNot Nothing AndAlso CInt(ndpKey.GetValue("Release")) < 528040 Then OldEnvironmentAssert()
End Using
If CInt(RegistryUtils.TryRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full", "Release", 999999)) < 528040 Then OldEnvironmentAssert()
Catch ex As Exception
Logger.Warn(ex, "检查 .NET Framework 版本失败")
End Try
@@ -50,7 +50,7 @@ Public Class Application
Catch ex As Exception
Environment.Exit(ProcessReturnValues.Fail)
End Try
ElseIf e.Args(0).StartsWithF("--memory") Then
ElseIf e.Args(0) = "--memory" Then
'内存优化
Dim Ram = My.Computer.Info.AvailablePhysicalMemory
Try
@@ -73,7 +73,7 @@ Public Class Application
CheckPermissionWithException(Paths.Base & "PCL\")
Catch ex As Exception
MsgBox($"PCL 没有对当前文件夹的权限({Paths.Base}PCL\),请尝试:" & vbCrLf &
"1. 将 PCL 移动到其他文件夹" & If(Paths.Base.StartsWithF("C:", True), ",例如 C 盘和桌面以外的其他位置。", "。") & vbCrLf &
"1. 将 PCL 移动到其他文件夹" & If(PathUtils.GetDiskName(Paths.Base) = "C"c, ",例如 C 盘和桌面以外的其他位置。", "。") & vbCrLf &
"2. 删除当前目录中的 PCL 文件夹,然后再试。" & vbCrLf &
"3. 右键 PCL 选择属性,打开 兼容性 中的 以管理员身份运行此程序。",
MsgBoxStyle.Critical, "运行环境错误")
@@ -84,7 +84,7 @@ RetryCacheCheck:
DirectoryUtils.Create(PathTemp)
CheckPermissionWithException(PathTemp)
Catch ex As Exception
If PathTemp = Path.GetTempPath() & "PCL\" Then
If PathUtils.IsSame(PathTemp, Path.GetTempPath() & "PCL\") Then
MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDisplay(True), "缓存文件夹不可用")
Else
MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDisplay(True), "缓存文件夹不可用")
@@ -95,8 +95,6 @@ RetryCacheCheck:
End Try
DirectoryUtils.Create(PathTemp & "Cache\")
DirectoryUtils.Create(Paths.AppDataThenName)
'要求单例
WaitingMutex(e)
'设置 ToolTipService 默认值
ToolTipService.InitialShowDelayProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(100))
'设置网络配置默认值
@@ -218,7 +216,7 @@ RetryCacheCheck:
'动态 DLL 加载
Private Sub New() '这里必须尽早调用,且不能使用任何库,否则加载 MeloongCore 就会导致崩溃
Static Prefixes As String() = {"NAudio", "Newtonsoft.Json", "Ookii.Dialogs.Wpf", "Imazen.WebP", "CacheCow.Common", "CacheCow.Client.FileStore", "CacheCow.Client", "ThrottleDebounce", "System.Net.Http.Formatting", "PCLCS", "MeloongCore.Wpf", "MeloongCore"}
Static Prefixes As String() = {"NAudio", "Newtonsoft.Json", "Ookii.Dialogs.Wpf", "Imazen.WebP", "CacheCow.Common", "CacheCow.Client.FileStore", "CacheCow.Client", "ThrottleDebounce", "System.Net.Http.Formatting", "Microsoft.Win32.Registry", "PCLCS", "MeloongCore.Wpf", "MeloongCore"}
Static LoadedAssemblies As New ConcurrentDictionary(Of String, Lazy(Of Assembly))(StringComparer.Ordinal) '缓存
AddHandler AppDomain.CurrentDomain.AssemblyResolve,
Function(sender As Object, Args As ResolveEventArgs) As Assembly
@@ -234,17 +232,15 @@ RetryCacheCheck:
End Function
End Sub
Private Shared Sub ExtractLibwebp() '这个方法会调用 ModBase,进而调用 MeloongCore,所以不能放在 New 里
SetDllDirectory(PathPure.TrimEnd("\"c))
SetDllDirectory(PathUtils.RemoveSlashSuffix(PathPure.Value))
Try
ExtractResources(PathPure & "libwebp.dll", "libwebp64")
ExtractResources(PathPure.Value & "libwebp.dll", "libwebp64")
Catch ex As Exception
Logger.Warn(ex, "写入 libwebp.dll 失败") '防止同时加载多个图片时,同时写入文件导致文件占用,进而导致崩溃
End Try
End Sub
Private Declare Function SetDllDirectory Lib "kernel32" Alias "SetDllDirectoryA" (lpPathName As String) As Boolean
'切换窗口
'控件模板事件
Private Sub MyIconButton_Click(sender As Object, e As EventArgs)
Select Case Settings.Get(Of McLoginType)("LoginType")
@@ -290,12 +286,4 @@ RetryCacheCheck:
End Select
End Sub
Public Shared ShowingTooltips As New List(Of Border)
Private Sub TooltipLoaded(sender As Border, e As EventArgs)
ShowingTooltips.Add(sender)
End Sub
Private Sub TooltipUnloaded(sender As Border, e As RoutedEventArgs)
ShowingTooltips.Remove(sender)
End Sub
End Class
@@ -44,8 +44,8 @@ Namespace Controls.Behaviors
Private Shared Sub AddCommandBindingsToTextBox(tb As TextBox)
tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyTextBox))
tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutTextBox))
tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteTextBox))
tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutTextBox, AddressOf CanEditTextBox))
tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteTextBox, AddressOf CanEditTextBox))
End Sub
Private Shared Sub AddCommandBindingsToRichTextBox(rtb As RichTextBox)
@@ -58,42 +58,61 @@ Namespace Controls.Behaviors
dg.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyDataGrid))
End Sub
Private Shared Sub SetClipboardText(text As String)
Forms.Clipboard.SetDataObject(text, False, retryTimes:=10, retryDelay:=100) '实际的复制
Try
System.Windows.Clipboard.Flush() '将数据持久化到系统剪贴板,避免应用程序退出后数据丢失
Catch ex As Exception '持久化失败不抛出异常
Logger.Warn(ex, $"剪贴板持久化失败(错误码:0x{ex.HResult:X8})")
End Try
End Sub
Private Shared Sub OnCopyTextBox(sender As Object, e As ExecutedRoutedEventArgs)
Dim tb = TryCast(sender, TextBox)
If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return
Try
Forms.Clipboard.Clear()
Forms.Clipboard.SetDataObject(tb.SelectedText, True)
SetClipboardText(tb.SelectedText)
Catch ex As Exception
Logger.Warn(ex, "复制失败,请稍后再试", LogBehavior.Toast)
Logger.Warn(ex, $"复制失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
End Try
e.Handled = True
End Sub
Private Shared Sub CanEditTextBox(sender As Object, e As CanExecuteRoutedEventArgs)
Dim tb = TryCast(sender, TextBox)
e.CanExecute = tb IsNot Nothing AndAlso Not tb.IsReadOnly AndAlso
(e.Command IsNot ApplicationCommands.Cut OrElse tb.SelectionLength > 0)
e.Handled = True
End Sub
Private Shared Sub OnCutTextBox(sender As Object, e As ExecutedRoutedEventArgs)
Dim tb = TryCast(sender, TextBox)
If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return
e.Handled = True
If tb Is Nothing OrElse tb.IsReadOnly OrElse tb.SelectionLength <= 0 Then Return
Try
Forms.Clipboard.Clear()
Forms.Clipboard.SetDataObject(tb.SelectedText, True)
SetClipboardText(tb.SelectedText)
Catch ex As Exception
Logger.Warn(ex, "剪切失败,请稍后再试", LogBehavior.Toast)
Logger.Warn(ex, $"剪切失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
Return
End Try
tb.SelectedText = String.Empty
e.Handled = True
End Sub
Private Shared Sub OnPasteTextBox(sender As Object, e As ExecutedRoutedEventArgs)
Dim tb = TryCast(sender, TextBox)
If tb Is Nothing Then Return
e.Handled = True
If tb Is Nothing OrElse tb.IsReadOnly Then Return
Try
If Forms.Clipboard.ContainsText() Then
Dim pasteText = Forms.Clipboard.GetText()
Dim pasteText = Retrier.Attempt(func:=
Function(Attempt) As String
Return If(Forms.Clipboard.ContainsText(), Forms.Clipboard.GetText(), Nothing)
End Function, maxAttempts:=10, delay:=Function() TimeSpan.FromMilliseconds(100))
If pasteText IsNot Nothing Then
'将换行符替换为空格(#7825)
If Not tb.AcceptsReturn Then pasteText = pasteText.ReplaceLineEndings(" ", mergeMultiple:=True)
@@ -105,10 +124,8 @@ Namespace Controls.Behaviors
tb.SelectionLength = 0
End If
Catch ex As Exception
Logger.Warn(ex, "粘贴失败,请稍后再试", LogBehavior.Toast)
Logger.Warn(ex, $"粘贴失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
End Try
e.Handled = True
End Sub
Private Shared Sub OnCopyRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
@@ -119,10 +136,9 @@ Namespace Controls.Behaviors
If String.IsNullOrEmpty(textRange.Text) Then Return
Try
Forms.Clipboard.Clear()
Forms.Clipboard.SetDataObject(textRange.Text, True)
SetClipboardText(textRange.Text)
Catch ex As Exception
Logger.Warn(ex, "复制失败,请稍后再试", LogBehavior.Toast)
Logger.Warn(ex, $"复制失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
End Try
e.Handled = True
@@ -134,16 +150,16 @@ Namespace Controls.Behaviors
Dim selection = New TextRange(rtb.Selection.Start, rtb.Selection.End)
If String.IsNullOrEmpty(selection.Text) Then Return
e.Handled = True
Try
Forms.Clipboard.Clear()
Forms.Clipboard.SetDataObject(selection.Text, True)
SetClipboardText(selection.Text)
Catch ex As Exception
Logger.Warn(ex, "剪切失败,请稍后再试", LogBehavior.Toast)
Logger.Warn(ex, $"剪切失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
Return
End Try
selection.Text = String.Empty
e.Handled = True
End Sub
Private Shared Sub OnPasteRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
@@ -151,9 +167,11 @@ Namespace Controls.Behaviors
If rtb Is Nothing Then Return
Try
If Not Forms.Clipboard.ContainsText() Then Return
Dim pasteText = Forms.Clipboard.GetText()
Dim pasteText = Retrier.Attempt(func:=
Function(Attempt) As String
Return If(Forms.Clipboard.ContainsText(), Forms.Clipboard.GetText(), Nothing)
End Function, maxAttempts:=2, delay:=Function() TimeSpan.FromMilliseconds(200))
If pasteText Is Nothing Then Return
Dim selection = rtb.Selection
selection.Text = pasteText
@@ -162,7 +180,7 @@ Namespace Controls.Behaviors
rtb.CaretPosition = caretPos
rtb.Selection.Select(caretPos, caretPos)
Catch ex As Exception
Logger.Warn(ex, "粘贴失败,请稍后再试", LogBehavior.Toast)
Logger.Warn(ex, $"粘贴失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
End Try
e.Handled = True
@@ -187,10 +205,9 @@ Namespace Controls.Behaviors
Dim sbStr = sb.ToString().TrimEnd(ControlChars.Cr, ControlChars.Lf)
Try
Forms.Clipboard.Clear()
Forms.Clipboard.SetDataObject(sbStr, True)
SetClipboardText(sbStr)
Catch ex As Exception
Logger.Warn(ex, "复制失败,请稍后再试", LogBehavior.Toast)
Logger.Warn(ex, $"复制失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
End Try
e.Handled = True
+29 -70
View File
@@ -102,73 +102,36 @@ Public Class MyCard
RunInUi(Sub() UseAnimation = RawUseAnimation, True)
End If
End Sub
Public Sub StackInstall()
StackInstall(SwapControl, SwapType, Title)
TriggerForceResize()
'填充
Private ContentBuilder As Action(Of Panel)
Public Sub SetContentBuilder(Builder As Action(Of Panel))
ContentBuilder = Builder
End Sub
Public Shared Sub StackInstall(ByRef Stack As StackPanel, Type As Integer, Optional CardTitle As String = "")
'这一部分的代码是好几年前留下的究极屎坑,当时还不知道该咋正确调用这种方法,就写了这么一坨屎
'但是现在……反正勉强能用……懒得改了就这样吧.jpg
'别骂了别骂了.jpg
If Stack.Tag Is Nothing Then Return
'排序
Dim HasDuplicates As Boolean = False
Select Case Type
Case 3
Stack.Tag = CType(Stack.Tag, List(Of DlOptiFineListEntry)).SortByComparison(Function(a, b) CompareVersionGE(a.DisplayName, b.DisplayName))
Case 4, 10
Stack.Tag = CType(Stack.Tag, List(Of DlLiteLoaderListEntry)).SortByComparison(Function(a, b) CompareVersionGE(a.Inherit, b.Inherit))
Case 6
Stack.Tag = CType(Stack.Tag, List(Of DlForgeVersionEntry)).OrderByDescending(Function(a) a).ToList
Case 8, 9
Stack.Tag = CType(Stack.Tag, List(Of ResourceVersion)).SortByComparison(Function(a, b) a.ReleaseDate > b.ReleaseDate)
HasDuplicates =
CType(Stack.Tag, List(Of ResourceVersion)).DistinctBy(Function(a) a.Display).Count <>
CType(Stack.Tag, List(Of ResourceVersion)).Count
End Select
'控件转换
Select Case Type
Case 6
ForgeDownloadListItemPreload(Stack, Stack.Tag, AddressOf ForgeSave_Click, True)
Case 8
PageDownloadResourceDetail.ResourceFilesCardPreload(Stack, Stack.Tag)
End Select
'实现控件虚拟化
For Each Data As Object In Stack.Tag
Select Case Type
Case 0
Stack.Children.Add(CType(Data, McInstance).ToListItem(AddressOf PageSelectRight.McInstanceListContent))
Case 2
Stack.Children.Add(McDownloadListItem(Data, AddressOf McDownloadMenuSave, True))
Case 3
Stack.Children.Add(OptiFineDownloadListItem(Data, AddressOf OptiFineSave_Click, True))
Case 6
Stack.Children.Add(ForgeDownloadListItem(Data, AddressOf ForgeSave_Click, True))
Case 7
'不能使用 AddressOf,这导致了 #535,原因完全不明,疑似是编译器 Bug
Stack.Children.Add(McDownloadListItem(Data, Sub(sender, e) FrmDownloadInstall.MinecraftSelected(sender, e), False))
Case 8
'若存在重复的版本名,则显示文件名而非版本名(#1344)
Stack.Children.Add(CType(Data, ResourceVersion).ToListItem(
AddressOf FrmDownloadResourceDetail.Save_Click, IsBadDisplay:=HasDuplicates))
Case 9
'若存在重复的版本名,则显示文件名而非版本名(#1344)
Stack.Children.Add(CType(Data, ResourceVersion).ToListItem(
AddressOf FrmDownloadResourceDetail.Install_Click, AddressOf FrmDownloadResourceDetail.Save_Click, IsBadDisplay:=HasDuplicates))
Case 10
Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf LiteLoaderSave_Click, True))
Case 11
Stack.Children.Add(CType(Data, HelpEntry).ToListItem)
Case 12
Stack.Children.Add(FabricDownloadListItem(CType(Data, JObject), AddressOf FrmDownloadInstall.Fabric_Selected))
Case 13
Stack.Children.Add(NeoForgeDownloadListItem(Data, AddressOf NeoForgeSave_Click, True))
Case Else
Logger.Error($"未知的虚拟化种类:{Type}")
End Select
Public Sub SetContentBuilder(Of T)(Items As IEnumerable(Of T), ItemBuilder As Func(Of T, UIElement))
SetContentBuilder(
Sub(Stack)
For Each Item In Items
Stack.Children.Add(ItemBuilder(Item))
Next
Stack.Children.Add(New FrameworkElement With {.Height = 18}) '下边距,同时适应折叠
Stack.Tag = Nothing
Stack.Children.Add(New FrameworkElement With {.Height = 18})
End Sub)
End Sub
''' <summary>
''' 使用 ContentBuilder 生成卡片内容。
''' </summary>
Public Sub BuildContent()
If ContentBuilder Is Nothing Then Return
Dim Stack = TryCast(SwapControl, Panel)
If Stack Is Nothing Then Return
Try
ContentBuilder(Stack)
ContentBuilder = Nothing
Catch
Stack.Children.Clear()
Throw
End Try
TriggerForceResize()
End Sub
'动画
@@ -278,10 +241,6 @@ Public Class MyCard
Public SwapControl As FrameworkElement
Public Property CanSwap As Boolean = False
''' <summary>
''' 被折叠的种类,用于控件虚拟化。
''' </summary>
Public Property SwapType As Integer
''' <summary>
''' 是否已被折叠。
''' </summary>
Public Property IsSwapped As Boolean
@@ -293,7 +252,7 @@ Public Class MyCard
_IsSwapped = value
If SwapControl Is Nothing Then Return
'展开
If Not IsSwapped AndAlso TypeOf SwapControl Is StackPanel Then StackInstall(SwapControl, SwapType, Title)
If Not IsSwapped Then BuildContent()
'若尚未加载,会在 Loaded 事件中触发无动画的折叠,不需要在这里进行
If Not IsLoaded Then Return
'更新样式
+1 -1
View File
@@ -90,7 +90,7 @@ Public Class MyImage
Catch ex As Exception
Logger.Warn(ex, $"加载图片失败({value})")
Try
If value.StartsWithF(PathTemp) Then FileUtils.Delete(value)
If value.StartsWithF(PathTemp, True) Then FileUtils.Delete(value)
Catch
End Try
End Try
@@ -508,7 +508,8 @@ Public Class MyListItem
RaiseEvent Click(sender, e)
If e.Handled Then Return
'触发自定义事件
If CustomEventService.GetEventType(sender) <> CustomEvent.EventType.None Then
If CustomEventService.GetEventType(sender) <> CustomEvent.EventType.None OrElse
CustomEventService.GetEvents(sender).Events.Count > 0 Then
RaiseCustomEvent()
e.Handled = True
End If
@@ -76,8 +76,7 @@ Public Class MyLoading
If Ex Is Nothing Then
LabText.Text = "未知错误"
Else
Ex = Ex.RootException()
LabText.Text = If(Ex.IsBadNetwork(), "网络环境不佳,请稍后再试,或使用 VPN 改善网络环境", Ex.Message)
LabText.Text = Ex.GetDisplay(False)
End If
Else
LabText.Text = TextError
@@ -18,10 +18,6 @@ Public Class MyScrollViewer
End If
e.Handled = True
PerformVerticalOffsetDelta(-e.Delta)
'关闭 Tooltip (#2552)
For Each TooltipBorder In Application.ShowingTooltips
AniStart(AaOpacity(TooltipBorder, -1, 100), $"Hide Tooltip {GetUuid()}")
Next
End Sub
Public Sub PerformVerticalOffsetDelta(Delta As Double)
AniStart(
+2 -2
View File
@@ -169,8 +169,8 @@
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Bottom" Grid.Row="1" Margin="15">
<local:MyExtraButton x:Name="BtnExtraBack" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="返回顶部" Visibility="Collapsed"
Logo="M858.496 188.9024 173.1072 188.9024c-30.2848 0-54.8352-24.5504-54.8352-54.8352L118.272 106.6496c0-30.2848 24.5504-54.8352 54.8352-54.8352l685.3888 0c30.2848 0 54.8352 24.5504 54.8352 54.8352l0 27.4176C913.3312 164.352 888.7808 188.9024 858.496 188.9024L858.496 188.9024zM150.6048 550.8608c0 0 300.0064-240.3584 303.0272-243.328 13.9776-13.5936 31.1808-21.8624 48.8192-24.7552 1.7152-0.3072 3.4304-0.5888 5.1456-0.768 2.7392-0.3072 5.4528-0.3584 8.192-0.3328 2.7392-0.0256 5.4272 0.0256 8.1664 0.3328 1.7408 0.1792 3.4304 0.4864 5.1456 0.768 17.664 2.8928 34.8672 11.1616 48.8192 24.7552 3.0464 2.944 303.0016 243.328 303.0016 243.328 32.384 31.5136 29.6192 63.9744-2.7392 95.5136-32.3328 31.5392-75.648 2.9696-108.0064-28.544l-185.8816-147.1232 0 485.8368c0 30.3104-24.5248 54.8608-54.8352 54.8608l-27.392 0c-30.2848 0-54.8352-24.5504-54.8352-54.8608L447.232 470.7072l-185.8304 147.0976c-32.3584 31.5392-75.6992 60.1344-108.032 28.5696C121.0368 614.8352 118.272 582.3744 150.6048 550.8608L150.6048 550.8608zM150.6048 550.8608" />
<local:MyExtraButton x:Name="BtnExtraDownload" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="下载管理" Visibility="Collapsed"
Logo="M955 610h-59c-15 0-29 13-29 29v196c0 15-13 29-29 29h-649c-15 0-29-13-29-29v-196c0-15-13-29-29-29h-59c-15 0-29 13-29 29V905c0 43 35 78 78 78h787c43 0 78-35 78-78V640c0-15-13-29-29-29zM492 740c11 11 29 11 41 0l265-265c11-11 11-29 0-41l-41-41c-11-11-29-11-41 0l-110 110c-11 11-33 3-33-13V68C571 53 555 39 541 39h-59c-15 0-29 13-29 29v417c0 17-21 25-33 13l-110-110c-11-11-29-11-41 0L226 433c-11 11-11 29 0 41L492 740z" />
<local:MyExtraButton x:Name="BtnExtraDownload" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="任务管理" Visibility="Collapsed"
LogoScale="1.1" Logo="M484.693 225.28 314.027 389.12l-68.267-68.267c-13.653-13.653-40.96-13.653-47.787 0-13.653 13.654-13.653 34.134 0 47.787l95.574 95.573c13.653 13.654 34.133 13.654 47.786 0L532.48 273.067c13.653-13.654 13.653-34.134 0-47.787s-34.133-13.653-47.787 0M819.2 341.333H614.4c-20.48 0-34.133 13.654-34.133 34.134S593.92 409.6 614.4 409.6h204.8c20.48 0 34.133-13.653 34.133-34.133s-13.653-34.134-34.133-34.134m0 341.334H614.4c-20.48 0-34.133 13.653-34.133 34.133s13.653 34.133 34.133 34.133h204.8c20.48 0 34.133-13.653 34.133-34.133s-13.653-34.133-34.133-34.133m-477.867-102.4c-75.093 0-136.533 61.44-136.533 136.533s61.44 136.533 136.533 136.533 136.534-61.44 136.534-136.533-61.44-136.533-136.534-136.533m0 204.8c-40.96 0-68.266-27.307-68.266-68.267s27.306-68.267 68.266-68.267S409.6 675.84 409.6 716.8s-27.307 68.267-68.267 68.267" />
<local:MyExtraButton x:Name="BtnExtraApril" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="投降" Visibility="Collapsed"
Logo="M124.96 32c-28.64 0-60.064 23.2-60.064 51.904v856.192c0 28.576 30.688 51.904 59.296 51.904s60.064-23.328 60.064-51.904V83.904C184.256 55.2 153.536 32 124.96 32z m616.352 71.264c-168.416 0-168.416-69.024-336.8-69.024-97.216 0-172.256 45.344-172.256 45.344l-0.736 516.736s75.776-45.28 172.96-45.28c168.416 0 168.416 69.024 336.8 69.024 105.344 0 211.168-55.328 211.168-55.328V48.064c0.032 0-105.792 55.2-211.136 55.2z m0 0" />
<local:MyExtraButton x:Name="BtnExtraShutdown" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="关闭 Minecraft" Visibility="Collapsed"
+76 -22
View File
@@ -11,6 +11,22 @@ Public Class FormMain
Dim FeatureList As New List(Of KeyValuePair(Of Integer, String))
'统计更新日志条目
If BuildType = BuildTypes.Release Then
If LastVersion < 413 Then 'Release 2.99.0.0
FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "删除:为遵守相关法律法规,PCL 暂时不再提供 MC 下载功能,非常抱歉 qwq……"))
End If
If LastVersion < 411 Then 'Release 2.14.0.0
FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "新增:允许修改、添加、移除、更新已安装版本的各个组件,可从版本设置中进入"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:启动游戏时允许按 Esc 取消"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:允许复制主页的启动日志中的内容"))
FeatureCount += 19
BugCount += 26
End If
If LastVersion < 408 Then 'Release 2.13.1.1
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:版本设置中,添加打开截图文件夹的快捷方式"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:导出整合包时,可以勾选导出光影设置文件"))
FeatureCount += 23
BugCount += 28
End If
If LastVersion < 406 Then 'Release 2.13.0.1
FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "新增:重做 Java 管理与相关设置,允许调整 Java 优先级、指定 Java 版本范围等"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:导出整合包时允许自动导出版本文件夹中的 Java"))
@@ -87,6 +103,27 @@ Public Class FormMain
'3:BUG+ IMP* FEAT-
'2:BUG* IMP-
'1:BUG-
If LastVersion < 414 Then 'Snapshot 2.99.0.0
FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "删除:为遵守相关法律法规,PCL 暂时不再提供 MC 下载功能,非常抱歉 qwq……"))
End If
If LastVersion < 412 Then 'Snapshot 2.14.0.0
FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "新增:允许修改、添加、移除、更新已安装版本的各个组件,可从版本设置中进入"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:启动游戏时允许按 Esc 取消"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:允许复制主页的启动日志中的内容"))
FeatureCount += 19
BugCount += 26
End If
If LastVersion < 410 Then 'Snapshot 2.13.1.1
If LastVersion = 409 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:更新 Mod 时可能更新成另一个 Mod 加载器的版本"))
FeatureCount += 2
BugCount += 6
End If
If LastVersion < 409 Then 'Snapshot 2.13.1.0
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:版本设置中,添加打开截图文件夹的快捷方式"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:导出整合包时,可以勾选导出光影设置文件"))
FeatureCount += 21
BugCount += 22
End If
If LastVersion < 407 Then 'Snapshot 2.13.0.1
If LastVersion = 405 Then
FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:使用部分主页预设时崩溃"))
@@ -150,7 +187,7 @@ Public Class FormMain
If LastVersion < 385 Then 'Snapshot 2.12.5
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法下载 NeoForge 26.1"))
If LastVersion = 384 Then
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:下载可能失败,提示下载管理刷新线程出错"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:下载可能失败,提示任务管理刷新线程出错"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法保存选择的 Minecraft 文件夹"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:使用中文搜索 Mod 时,部分结果会忽略筛选条件"))
End If
@@ -786,8 +823,14 @@ Public Class FormMain
' 在没有弹窗时:继续检查……
'==========================
'按 ESC 返回上一级
If e.Key = Key.Escape Then TriggerPageBack()
'按 ESC 取消启动,或返回上一级
If e.Key = Key.Escape Then
If PageCurrent = FormMain.PageType.Launch AndAlso McLaunchLoader.State = LoadState.Loading Then
FrmLaunchLeft.BtnCancel_Click()
Return
End If
TriggerPageBack()
End If
'更改隐藏版本可见性
If e.Key = Key.F11 AndAlso PageCurrent = FormMain.PageType.InstanceSelect Then
FrmSelectRight.ShowHidden = Not FrmSelectRight.ShowHidden
@@ -998,14 +1041,14 @@ Public Class FormMain
If FilePathList.Count > 1 Then
'必须要求全部为 jar 文件
For Each File In PathList
If Not {"jar", "litemod", "disabled", "old"}.Contains(File.AfterLast(".").Lower) Then
If Not {"jar", "litemod", "disabled", "old"}.Contains(PathUtils.GetExtension(File)) Then
Hint("一次请只拖入一个文件!", HintType.Red)
Return
End If
Next
End If
'主页
Dim Extension As String = FilePath.AfterLast(".").Lower
Dim Extension As String = PathUtils.GetExtension(FilePath)
If Extension = "xaml" Then
Logger.Info("文件后缀为 XAML,作为主页加载")
If FileUtils.Exists(Paths.Base & "PCL\Custom.xaml") Then
@@ -1026,18 +1069,24 @@ Public Class FormMain
If PageInstanceMod.InstallMods(PathList) Then Return
'安装整合包
If {"zip", "rar", "mrpack"}.Any(Function(t) t = Extension) Then '部分压缩包是 zip 格式但后缀为 rar,总之试一试
Logger.Info("文件为压缩包,尝试作为整合包安装")
Hint("正在读取压缩包……")
Try
Logger.Info("文件为压缩包,尝试作为整合包安装")
ModpackInstall(FilePath)
RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.TaskManager))
Return
Catch ex As Exception
If RestrictMcDownload AndAlso ex.Message = "为遵守相关法律法规,PCL 暂时不再提供 MC 下载功能,非常抱歉 qwq……" Then
Hint(ex.Message, HintType.Red)
Return
End If
If ex.IsCanceled Then Return
'安装失败,继续往后尝试
End Try
End If
'RAR 处理
If Extension = "rar" Then
Hint("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试!")
Hint("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试!", HintType.Red)
Return
End If
'错误报告分析
@@ -1053,7 +1102,7 @@ Public Class FormMain
Logger.Error(ex, "自主错误报告分析失败")
End Try
'未知操作
Hint("PCL 无法确定应当执行的文件拖拽操作……")
Hint("PCL 无法确定适合的操作……", HintType.Red)
End Sub, "文件拖拽")
End Sub
@@ -1157,9 +1206,9 @@ Public Class FormMain
''' </summary>
InstanceSelect = 5
''' <summary>
''' 下载管理。这是一个副页面。
''' 任务管理。这是一个副页面。
''' </summary>
DownloadManager = 6
TaskManager = 6
''' <summary>
''' 版本设置。这是一个副页面。
''' </summary>
@@ -1195,9 +1244,10 @@ Public Class FormMain
OtherTest = 2
InstanceOverall = 0
InstanceSetup = 1
InstanceMod = 2
InstanceModDisabled = 3
InstanceExport = 4
InstanceModify = 2
InstanceMod = 3
InstanceModDisabled = 4
InstanceExport = 5
End Enum
''' <summary>
''' 获取次级页面的名称。若并非次级页面则返回空字符串,故可以以此判断是否为次级页面。
@@ -1206,8 +1256,8 @@ Public Class FormMain
Select Case Stack.Page
Case PageType.InstanceSelect
Return "版本选择"
Case PageType.DownloadManager
Return "下载管理"
Case PageType.TaskManager
Return "任务管理"
Case PageType.InstanceSetup
Return "版本设置 - " & If(PageInstanceLeft.Instance Is Nothing, "未知版本", PageInstanceLeft.Instance.Name)
Case PageType.ResourceDetail
@@ -1362,6 +1412,10 @@ Public Class FormMain
''' </summary>
Private Sub PageChangeActual(Stack As PageStackData, Optional SubType As PageSubType = -1)
If PageCurrent = Stack AndAlso (PageCurrentSub = SubType OrElse SubType = -1) Then Return
If Stack.Page = PageType.InstanceSetup AndAlso PageInstanceLeft.Instance IsNot Nothing AndAlso McInstanceLock.IsLocked(PageInstanceLeft.Instance) Then
Hint("该版本正在被修改,暂时无法进入版本设置!", HintType.Red)
Return
End If
AniControlEnabled += 1
Try
@@ -1429,7 +1483,7 @@ Public Class FormMain
If FrmSelectLeft Is Nothing Then FrmSelectLeft = New PageSelectLeft
If FrmSelectRight Is Nothing Then FrmSelectRight = New PageSelectRight
PageChangeAnim(FrmSelectLeft, FrmSelectRight)
Case PageType.DownloadManager '下载管理
Case PageType.TaskManager '任务管理
If FrmSpeedLeft Is Nothing Then FrmSpeedLeft = New PageSpeedLeft
If FrmSpeedRight Is Nothing Then FrmSpeedRight = New PageSpeedRight
PageChangeAnim(FrmSpeedLeft, FrmSpeedRight)
@@ -1449,7 +1503,7 @@ Public Class FormMain
BtnExtraApril.ShowRefresh()
#End Region
Logger.Info($"切换主要页面:{Stack}, {SubType}")
Logger.Info($"切换主要页面:{Stack.Page}, {SubType}")
Catch ex As Exception
Logger.Error(ex, $"切换主要页面失败(ID {PageCurrent.Page})")
Finally
@@ -1461,8 +1515,8 @@ Public Class FormMain
AniStop("PageLeft PageChange") '停止左边栏变更导致的右页面切换动画,防止它与本动画一起触发多次 PageOnEnter
AniControlEnabled += 1
'清除新页面关联性
If Not IsNothing(TargetLeft.Parent) Then TargetLeft.SetValue(ContentPresenter.ContentProperty, Nothing)
If Not IsNothing(TargetRight) AndAlso Not IsNothing(TargetRight.Parent) Then TargetRight.SetValue(ContentPresenter.ContentProperty, Nothing)
If TargetLeft.Parent IsNot Nothing Then TargetLeft.SetValue(ContentPresenter.ContentProperty, Nothing)
If TargetRight IsNot Nothing AndAlso TargetRight.Parent IsNot Nothing Then TargetRight.SetValue(ContentPresenter.ContentProperty, Nothing)
PageLeft = TargetLeft
PageRight = TargetRight
'触发页面通用动画
@@ -1603,12 +1657,12 @@ Public Class FormMain
MusicControlNext()
End Sub
'下载管理
'任务管理
Private Sub BtnExtraDownload_Click(sender As Object, e As EventArgs) Handles BtnExtraDownload.Click
PageChange(PageType.DownloadManager)
PageChange(PageType.TaskManager)
End Sub
Private Function BtnExtraDownload_ShowCheck() As Boolean
Return HasDownloadingTask() AndAlso Not PageCurrent = PageType.DownloadManager
Return HasDownloadingTask() AndAlso Not PageCurrent = PageType.TaskManager
End Function
'投降
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -79,7 +79,7 @@ Public Module ModAnimation
''' 已经执行的动画长度。如果为负数则为延迟。
''' </summary>
''' <remarks></remarks>
Public TimeFinished As Integer
Public TimeFinished As Double
''' <summary>
''' 已经完成的百分比。
''' </summary>
@@ -853,9 +853,8 @@ Sleeper:
''' <summary>
''' 动画定时器事件。
''' </summary>
Public Sub AniTimer(DeltaTick As Integer)
Public Sub AniTimer(DeltaTick As Double)
Try
If DeltaTick / AniSpeed > 100 Then Logger.Info($"两个动画帧间隔 {DeltaTick} ms")
Dim i As Integer = -1
'循环每个动画组
@@ -1034,6 +1033,7 @@ NextAni:
End If
Case AniType.Code
Ani.TimeFinished = Ani.TimeTotal
CType(Ani.Value, ThreadStart)()
Case AniType.ScaleTransform
+19 -62
View File
@@ -6,12 +6,12 @@ Public Module ModBase
#Region "声明"
'下列版本信息由更新器自动修改
Public Const VersionBaseName As String = "2.13.0.1" '显示用版本名
Public Const VersionBaseName As String = "2.99.0.0" '显示用版本名
Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
#If RELEASE Then
Public Const VersionCode As Integer = 406 '正式版
Public Const VersionCode As Integer = 413 '正式版
#Else
Public Const VersionCode As Integer = 407 '快照版
Public Const VersionCode As Integer = 414 '快照版
#End If
'版本信息
@@ -47,7 +47,7 @@ Public Module ModBase
''' <summary>
''' 程序的缓存文件夹路径,以 \ 结尾。
''' </summary>
Public PathTemp As String = If(Settings.Get(Of String)("SystemSystemCache") = "", Path.GetTempPath() & "PCL\", Settings.Get(Of String)("SystemSystemCache")).ToString.Replace("/", "\").TrimEnd("\") & "\"
Public PathTemp As String = PathUtils.AddSlashSuffix(If(Settings.Get(Of String)("SystemSystemCache") = "", Path.GetTempPath() & "PCL\", Settings.Get(Of String)("SystemSystemCache")).Replace("/", "\"))
''' <summary>
''' 当前程序的语言。
''' </summary>
@@ -75,7 +75,7 @@ Public Module ModBase
''' <summary>
''' 系统盘盘符,以 \ 结尾。例如 “C:\”。
''' </summary>
Public OsDrive As String = Environment.GetLogicalDrives().Where(Function(p) Directory.Exists(p)).First.Upper.First & ":\" '#3799
Public OsDrive As String = Environment.GetLogicalDrives().First(Function(p) Directory.Exists(p)) '#3799
Public ReadOnly Property ModeDebug As Boolean
Get
@@ -465,57 +465,7 @@ Public Module ModBase
#Region "文件"
'=============================
' 注册表
'=============================
''' <summary>
''' 读取注册表键。如果失败则返回默认值。
''' </summary>
Public Function ReadReg(Key As String, Optional DefaultValue As String = "") As String
Try
Return If(My.Computer.Registry.CurrentUser.OpenSubKey("Software\" & RegFolder, False)?.GetValue(Key), DefaultValue)
Catch ex As Exception
Logger.Error(ex, $"读取注册表出错:{Key}", LogBehavior.Toast)
Return DefaultValue
End Try
End Function
''' <summary>
''' 写入注册表键。
''' </summary>
Public Sub WriteReg(Key As String, Value As String, Optional ThrowException As Boolean = False)
Try
Dim SubKey As Microsoft.Win32.RegistryKey = My.Computer.Registry.CurrentUser.OpenSubKey("Software\" & RegFolder, True)
If SubKey Is Nothing Then SubKey = My.Computer.Registry.CurrentUser.CreateSubKey("Software\" & RegFolder) '如果不存在就创建
SubKey.SetValue(Key, Value)
Catch ex As Exception
Logger.Warn(ex, $"写入注册表出错:{Key}", If(ThrowException, LogBehavior.Toast, LogBehavior.ToastIfDebug))
If ThrowException Then Throw
End Try
End Sub
''' <summary>
''' 是否存在某个注册表键。
''' </summary>
Public Function HasReg(Key As String) As Boolean
Return ReadReg(Key, Nothing) IsNot Nothing
End Function
''' <summary>
''' 删除注册表键。
''' </summary>
Public Sub DeleteReg(Key As String, Optional ThrowException As Boolean = False)
Try
Dim SubKey As Microsoft.Win32.RegistryKey = My.Computer.Registry.CurrentUser.OpenSubKey("Software\" & RegFolder, True)
If SubKey?.GetValue(Key) Is Nothing Then Return
SubKey.DeleteValue(Key)
Catch ex As Exception
Logger.Warn(ex, $"删除注册表出错:{Key}", If(ThrowException, LogBehavior.Toast, LogBehavior.ToastIfDebug))
If ThrowException Then Throw
End Try
End Sub
'=============================
'ini
'=============================
Private ReadOnly IniCache As New ConcurrentDictionary(Of String, ConcurrentDictionary(Of String, String))
''' <summary>
@@ -617,7 +567,8 @@ Public Module ModBase
End Try
End Sub
'文件校验
'权限检查
''' <summary>
''' 检查是否拥有某一文件夹的 I/O 权限。如果文件夹不存在,会返回 False。
''' </summary>
@@ -625,7 +576,7 @@ Public Module ModBase
Try
If String.IsNullOrEmpty(Folder) Then Return False
Folder = PathUtils.AddSlashSuffix(Folder)
If Folder.EndsWithF(":\System Volume Information\") OrElse Folder.EndsWithF(":\$RECYCLE.BIN\") Then Return False
If Folder.EndsWithF(":\System Volume Information\", True) OrElse Folder.EndsWithF(":\$RECYCLE.BIN\", True) Then Return False
If Not DirectoryUtils.Exists(Folder) Then Return False
Dim TestFilePath As String = $"{Folder}CheckPermission{GetUuid()}.txt"
FileUtils.Write(TestFilePath, "临时文件,用于检测该文件夹的权限是否正常。")
@@ -642,7 +593,7 @@ Public Module ModBase
Public Sub CheckPermissionWithException(Folder As String)
If String.IsNullOrWhiteSpace(Folder) Then Throw New ArgumentNullException("文件夹名不能为空!")
Folder = PathUtils.AddSlashSuffix(Folder)
If Folder.EndsWithF(":\System Volume Information\") OrElse Folder.EndsWithF(":\$RECYCLE.BIN\") Then Throw New UnauthorizedAccessException("没有对系统文件夹的权限!")
If Folder.EndsWithF(":\System Volume Information\", True) OrElse Folder.EndsWithF(":\$RECYCLE.BIN\", True) Then Throw New UnauthorizedAccessException("没有对系统文件夹的权限!")
If Not DirectoryUtils.Exists(Folder) Then Throw New DirectoryNotFoundException("文件夹不存在!")
Dim TestFilePath As String = $"{Folder}CheckPermission{GetUuid()}.txt"
FileUtils.Write(TestFilePath, "临时文件,用于检测该文件夹的权限是否正常。")
@@ -837,8 +788,8 @@ Public Module ModBase
''' <summary>
''' 可用于临时存放文件的,不含任何特殊字符的文件夹路径,以“\”结尾。
''' </summary>
Public PathPure As String = GetPureASCIIDir()
Private Function GetPureASCIIDir() As String
Public PathPure As New Lazy(Of String)(
Function()
If Paths.Base.IsAsciiOnly() Then
Return Paths.Base & "PCL\"
ElseIf Paths.AppDataThenName.IsAsciiOnly() Then
@@ -848,7 +799,7 @@ Public Module ModBase
Else
Return OsDrive & "ProgramData\PCL\"
End If
End Function
End Function)
''' <summary>
''' 指示接取到这个异常的函数进行重试。
@@ -894,6 +845,12 @@ Public Module ModBase
Return (Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000
End Function
''' <summary>
''' 获取十进制 Unix 时间戳。
''' </summary>
Public Function ToUnixTimestampUtc(Time As Date) As Long
Return (Time.ToUniversalTime().Ticks - 621355968000000000) / 10000000
End Function
''' <summary>
''' 时间戳转化为日期。
''' </summary>
Public Function GetDate(timeStamp As Integer) As Date
@@ -1076,7 +1033,7 @@ Public Module ModBase
Try
Retrier.Attempt(delay:=Function(Attempt) TimeSpan.FromMilliseconds(200), maxAttempts:=4, isRetryAllowed:=Function(ex) True, action:=
Sub()
RunInUi(
RunInUiWait(
Sub()
My.Computer.Clipboard.Clear()
If Not String.IsNullOrEmpty(Text) Then My.Computer.Clipboard.SetText(Text)
+16 -92
View File
@@ -217,8 +217,7 @@ Retry:
Optional Content As Object = Nothing, Optional Headers As Dictionary(Of String, String) = Nothing,
Optional SimulateBrowserHeaders As Boolean = False, Optional Timeout As Integer = 25000, Optional Encoding As Encoding = Nothing) As Byte()
If RunInUi() AndAlso Not Url.Contains("//127.") Then Throw New Exception("在 UI 线程执行了网络请求")
Dim Request As HttpRequestMessage = Nothing, CancelToken As CancellationTokenSource = Nothing,
Response As HttpResponseMessage = Nothing, HostIp As String = Nothing
Dim Request As HttpRequestMessage = Nothing, CancelToken As CancellationTokenSource = Nothing, Response As HttpResponseMessage = Nothing
Try
'构建 RequestClient
Directory.CreateDirectory(PathTemp & "Cache\Http\")
@@ -255,8 +254,6 @@ Retry:
Retrier.Attempt(delay:=Function() TimeSpan.FromMilliseconds(200), isRetryAllowed:=Function(ex) TypeOf ex Is FileNotFoundException OrElse TypeOf ex Is IOException, action:=
Sub(Attempt)
CancelToken?.Dispose() : CancelToken = New CancellationTokenSource(Timeout)
HostIp = DNSLookup(Request, CancelToken.Token)
If HostIp IsNot Nothing Then IPReliability.GetOrAdd(HostIp, -0.01) '预先降低一点,这样快速的重复请求会使用不同的 IP 以提高成功率
Dim ClonedRequest As HttpRequestMessage = Request.Clone() 'HttpRequestMessage 只能发送一次,重试时需要克隆一个新的
Try
Response = RequestClient.SendAsync(ClonedRequest, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
@@ -274,26 +271,22 @@ Retry:
End Using
End Using
If Not Response.IsSuccessStatusCode Then
RecordIPReliability(HostIp, -0.7)
Dim ResponseMessage = ResponseBytes.GetString(Encoding)
Throw New HttpRequestCodeException(
$"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Method},{Url},{HostIp}" &
$"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Method},{Url}" &
If(String.IsNullOrEmpty(ResponseMessage), "", vbCrLf & ResponseMessage), Response.StatusCode, ResponseMessage)
End If
'输出
RecordIPReliability(HostIp, 0.5)
Return ResponseBytes
Catch ex As HttpRequestException
Throw
Catch ex As TimeoutException
Throw New WebException($"连接服务器超时,请稍后再试,或使用 VPN 改善网络环境({Method}, {Url},IP:{HostIp})", WebExceptionStatus.Timeout)
Throw New WebException($"连接服务器超时,请稍后再试,或使用 VPN 改善网络环境({Method}, {Url})", WebExceptionStatus.Timeout)
Catch ex As Exception
ex.ThrowIfCanceled()
RecordIPReliability(HostIp, -1)
If TypeOf ex IsNot TaskCanceledException Then ex.ThrowIfCanceled() 'GetResultWithTimeout 会抛出 TaskCanceledException
If ex.IsBadNetwork Then
Throw New WebException($"网络请求失败,请稍后再试,或使用 VPN 改善网络环境({Method}, {Url},IP:{HostIp})", WebExceptionStatus.Timeout)
Throw New WebException($"网络请求失败,请稍后再试,或使用 VPN 改善网络环境({Method}, {Url})", WebExceptionStatus.Timeout)
Else
Throw New Exception($"网络请求出现意外异常({Method}, {Url},{HostIp})", ex)
Throw New Exception($"网络请求出现意外异常({Method}, {Url})", ex)
End If
Finally
Request?.Dispose()
@@ -304,72 +297,6 @@ Retry:
Private RequestClient As HttpClient = Nothing
Private RequestClientLock As New Object
''' <summary>
''' 进行 DNS 解析。它仅在选择的 IP 与系统默认的不一致时才对 URL 中的 Host 进行替换。
''' 返回请求时应使用的 IP。若解析失败,则返回 Nothing。
''' </summary>
Private Function DNSLookup(Request As HttpRequestMessage, CancelToken As CancellationToken) As String
Dim Host = Request.RequestUri.Host
'不对部分有严格 SNI 限制的域名进行 DNS 解析(#8295)
If {"mojang.com", "minecraft.net", "minecraftservices.com"}.Any(Function(h) Host.ContainsIgnoreCase(h)) Then Return Nothing
'若一分钟内已失败过,则不再重复尝试解析,以减少断网时的 Exception 数量
Dim LastFailureTime As Date
If DNSFailureRecord.TryGetValue(Host, LastFailureTime) Then
If Date.Now - LastFailureTime < New TimeSpan(0, 1, 0) Then
Return Nothing
Else
DNSFailureRecord.Remove(Host)
End If
End If
'初步 DNS 解析
Dim Candidates As IPAddress()
Try
Dim AsyncResult = Dns.BeginGetHostAddresses(Host, Nothing, Nothing)
If WaitHandle.WaitAny(New WaitHandle() {AsyncResult.AsyncWaitHandle, CancelToken.WaitHandle}) Then Throw New TimeoutException("DNS 解析超时")
Candidates = Dns.EndGetHostAddresses(AsyncResult).Distinct.ToArray
Catch ex As Exception
Logger.Warn(ex, $"DNS 解析失败({Host})")
DNSFailureRecord(Host) = Date.Now
Return Nothing
End Try
If Not Candidates.Any Then
Logger.Info($"DNS 解析无结果({Host})")
Return Nothing
End If
'若同时存在 IPv4 和 IPv6 地址,仅选择其中一类(因为 GFW 可能只屏蔽了 IPv4 或 IPv6)
Dim Reliabilities = Candidates.ToDictionary(Function(i) i, Function(i) IPReliability.GetOrDefault(i.ToString, 0))
Dim IPv4Targets = Candidates.Where(Function(i) i.AddressFamily = AddressFamily.InterNetwork).ToArray
Dim IPv6Targets = Candidates.Where(Function(i) i.AddressFamily = AddressFamily.InterNetworkV6).ToArray
If IPv4Targets.Any AndAlso IPv6Targets.Any Then
Dim IPv4Reliability = IPv4Targets.Max(Function(i) Reliabilities(i))
Dim IPv6Reliability = IPv6Targets.Max(Function(i) Reliabilities(i))
If Host = "api.modrinth.com" Then IPv6Reliability -= 0.1 '让 Modrinth 优先使用 IPv4 地址(#6887)
Logger.Trace(Function() $"DNS IPv4/IPv6 选择({Host}),IPv4 {IPv4Reliability:0.000},IPv6 {IPv6Reliability:0.000}")
Candidates = If(IPv4Reliability >= IPv6Reliability, IPv4Targets, IPv6Targets)
End If
'选择可靠度最高的 IP
Dim Target As IPAddress = Candidates.MaxBy(Function(i) Reliabilities(i))
Request.Headers.Host = Request.RequestUri.Authority
Request.RequestUri = (New UriBuilder(Request.RequestUri) With {.Host = If(Target.AddressFamily = AddressFamily.InterNetworkV6, $"[{Target}]", Target.ToString)}).Uri
Return Target.ToString
End Function
Private DNSFailureRecord As New ConcurrentDictionary(Of String, Date)
''' <summary>
''' 记录每个 IP 地址的请求可靠度。
''' 通常取值 -1 ~ +0.5,越高越好。未尝试过的 IP 应视为 0。
''' </summary>
Private IPReliability As New ConcurrentDictionary(Of String, Double)
''' <summary>
''' 根据请求结果,记录 IP 地址的可靠度。
''' </summary>
Private Sub RecordIPReliability(IP As String, Result As Double)
If IP Is Nothing Then Return
IPReliability.AddOrUpdate(IP,
Function() Result * 0.5,
Function(k, v) v * 0.5 + Result * 0.5
)
End Sub
#End Region
#Region "多线程下载引擎"
@@ -976,7 +903,7 @@ StartThread:
Logger.Log($"{LocalName}:开始,起始点 {Th.DownloadStart},{Th.Source.Url}", If(Th.DownloadStart = 0, LogLevel.Info, LogLevel.Trace))
Dim ResultStream As Stream = Nothing, HttpRequest As HttpRequestMessage = Nothing,
Response As HttpResponseMessage = Nothing, ResponseStream As Stream = Nothing,
CancelToken As CancellationTokenSource = Nothing, HostIp As String = Nothing
CancelToken As CancellationTokenSource = Nothing
'部分下载源真的特别慢,并且只需要一个请求,例如 Ping 为 20s,如果增长太慢,就会造成类似 2.5s 5s 7.5s 10s 12.5s... 的极大延迟
'延迟过长会导致某些特别慢的链接迟迟不被掐死
Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 15000) * (1 + Th.Source.FailCount), 30000)
@@ -987,7 +914,6 @@ StartThread:
HttpRequest = New HttpRequestMessage(HttpMethod.Get, Th.Source.Url)
SecretHeadersSign(Th.Source.Url, HttpRequest, SimulateBrowserHeaders)
CancelToken = New CancellationTokenSource(Timeout)
HostIp = DNSLookup(HttpRequest, CancelToken.Token) 'DNS 预解析
If Not Th.IsFirstThread Then HttpRequest.Headers.Range = New Headers.RangeHeaderValue(Th.DownloadStart, Nothing)
Dim ContentLength As Long = 0
Response = ThreadClient.SendAsync(HttpRequest, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
@@ -1119,15 +1045,13 @@ SourceBreak:
'本线程完成
Th.State = NetState.Finished
Logger.Trace(Function() $"{LocalName}:完成,已下载 {Th.DownloadDone}({Th.DownloadStart}~{Th.DownloadEnd})")
RecordIPReliability(HostIp, 0.5)
End If
Catch ex As Exception
If ex.IsBadNetwork OrElse TypeOf ex Is TaskCanceledException Then
Logger.Info(New TimeoutException($"{LocalName}:超时({Timeout}ms),{ex.Message},IP:{If(String.IsNullOrEmpty(HostIp), "自动", "")}"))
Logger.Info(New TimeoutException($"{LocalName}:超时({Timeout}ms),{ex.Message}"))
Else
Logger.Info(ex, $"{LocalName}:出错,IP:{HostIp}")
Logger.Info(ex, $"{LocalName}:出错")
End If
RecordIPReliability(HostIp, -0.7)
SourceFail(Th, ex, False)
Finally
'释放资源
@@ -1423,8 +1347,8 @@ Retry:
End Get
Set(value As Integer)
_FailCount = value
If State = LoadState.Loading AndAlso value >= Math.Min(10000, Math.Max(FileRemain * 5.5, NetTaskThreadLimit * 5.5 + 3)) Then
Logger.Warn($"由于同加载器中失败次数过多引发强制失败:连续失败了 {value} 次")
If State = LoadState.Loading AndAlso value >= Math.Min(10000, Math.Max(FileRemain * 8, NetTaskThreadLimit * 8 + 3)) Then
Logger.Error($"由于同加载器中失败次数过多引发强制失败:连续失败了 {value} 次", LogBehavior.ToastIfDebug)
On Error Resume Next
Dim ExList As New List(Of Exception)
For Each File In Files
@@ -1472,7 +1396,7 @@ FinishExCatch:
Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
If Input IsNot Nothing Then Files = New ConcurrentQueue(Of NetFile)(CType(Input, IEnumerable(Of NetFile)))
'去重
Files = New ConcurrentQueue(Of NetFile)(Files.DistinctBy(Function(a) a.LocalPath))
Files = New ConcurrentQueue(Of NetFile)(Files.DistinctBy(Function(a) PathUtils.ForCompare(a.LocalPath).Lower()))
'设置剩余文件数
SyncLock FileRemainLock
FileRemain += Files.Where(Function(f) f.State <> NetState.Finished).Count
@@ -1497,7 +1421,7 @@ FinishExCatch:
Next
If Not File.HasAvailableSource() Then Throw New ArgumentException("输入的下载链接不正确!")
File.LocalPath = File.LocalPath.Replace("/", "\")
If Not File.LocalPath.Lower.Contains(":\") Then Throw New ArgumentException("输入的本地文件地址不正确:" & File.LocalPath)
If Not File.LocalPath.Contains(":\") Then Throw New ArgumentException("输入的本地文件地址不正确:" & File.LocalPath)
If File.LocalPath.EndsWithF("\") Then Throw New ArgumentException("请输入含文件名的完整文件路径:" & File.LocalPath)
DirectoryUtils.Create(PathUtils.RemoveLastPart(File.LocalPath)) '创建目标文件夹
Next
@@ -1595,7 +1519,7 @@ FinishExCatch:
Select(Function(FolderName) (FolderName, File.LocalPath.IndexOfF(FolderName, True))).
Where(Function(kv) kv.Item2 >= 0).ToList
If Not TypeIndexes.Any Then
If File.LocalName.EndsWithF(".jar") Then
If PathUtils.GetExtension(File.LocalName) = "jar" Then
TypeIndexes.Add(("\versions\", 1)) '总是对 jar 进行版本文件检查,以包括另存为 jar 的情况
Else
Return Nothing
@@ -1623,7 +1547,7 @@ FinishExCatch:
Case "versions\"
'版本 jar 或 json:查找 MC 文件夹下的各个版本文件夹
For Each VersionFolder In VersionFolders
For Each Candidate In DirectoryUtils.EnumerateFiles(VersionFolder, searchPattern:="*." & PathUtils.GetLastPart(File.LocalPath).AfterLast(".").Lower)
For Each Candidate In DirectoryUtils.EnumerateFiles(VersionFolder, searchPattern:="*." & PathUtils.GetExtension(File.LocalPath))
If CheckCandidate(Candidate, File.Check) Then Return Candidate
Next
Next
@@ -1917,7 +1841,7 @@ FinishExCatch:
End Class
''' <summary>
''' 是否有正在进行中、需要在下载管理页面显示的下载任务?
''' 是否有正在进行中、需要在任务管理页面显示的下载任务?
''' </summary>
Public Function HasDownloadingTask(Optional IgnoreCustomDownload As Boolean = False) As Boolean
Return LoaderTaskbar.Any(
@@ -260,16 +260,16 @@ Public Class ValidateFolderName
Inherits Validate
Public Property Folder As String
Public Property UseMinecraftCharCheck As Boolean = True
Public Property IgnoreCase As Boolean = True
Private ReadOnly PathIgnore As IEnumerable(Of String)
Private ReadOnly IgnoreList As List(Of String)
Public Sub New()
End Sub
Public Sub New(Folder As String, Optional UseMinecraftCharCheck As Boolean = True, Optional IgnoreCase As Boolean = True)
Public Sub New(Folder As String, Optional UseMinecraftCharCheck As Boolean = True, Optional IgnoreList As List(Of String) = Nothing)
Me.Folder = Folder
Me.IgnoreCase = IgnoreCase
Me.UseMinecraftCharCheck = UseMinecraftCharCheck
On Error Resume Next
PathIgnore = DirectoryUtils.EnumerateDirectories(Folder)
Me.IgnoreList = IgnoreList
End Sub
Public Overrides Function Validate(Str As String) As String
Try
@@ -294,12 +294,14 @@ Public Class ValidateFolderName
If Str.RegexCheck(".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
'检查文件夹重名
Dim Arr As New List(Of String)
Dim folderName As String
If PathIgnore IsNot Nothing Then
For Each FolderPath In PathIgnore
Arr.Add(PathUtils.GetLastPart(FolderPath))
folderName = PathUtils.GetLastPart(FolderPath)
If IgnoreList Is Nothing OrElse Not IgnoreList.Contains(folderName, StringComparer.OrdinalIgnoreCase) Then Arr.Add(folderName)
Next
End If
Dim SameNameCheck = New ValidateExceptSame(Arr, "不可与现有文件夹重名!", IgnoreCase).Validate(Str)
Dim SameNameCheck = New ValidateExceptSame(Arr, "不可与现有文件夹重名!", True).Validate(Str)
If Not SameNameCheck = "" Then Return SameNameCheck
Return ""
Catch ex As Exception
@@ -380,13 +382,16 @@ Public Class ValidateFolderPath
Public Overrides Function Validate(Str As String) As String
'去除尾部斜线,统一为 \
Str = Str.Replace("/", "\")
If Not Str.TrimEnd("\").EndsWithF(":") Then Str = Str.TrimEnd("\")
If Not PathUtils.RemoveSlashSuffix(Str).EndsWithF(":") Then Str = PathUtils.RemoveSlashSuffix(Str)
'检查是否为空
Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
If LengthCheck <> "" Then Return LengthCheck
'检查长度
LengthCheck = New ValidateLength(1, 254).Validate(Str)
If LengthCheck <> "" Then Return LengthCheck
'检查 NTFS 8.3 文件名(#4505)
Dim Match = Str.RegexSeek(".{2,}~\d")
If Match IsNot Nothing Then Return $"文件夹名不能包含 {Match} 这一特殊格式!"
'检查开头
If Str.StartsWithF("\\Mac\") Then GoTo Fin
For Each Drive In Environment.GetLogicalDrives().Where(Function(p) DirectoryUtils.Exists(p))
@@ -413,8 +418,6 @@ Fin:
'检查特殊字符串
Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!").Validate(SubStr)
If Not InvalidStrCheck = "" Then Return InvalidStrCheck
'检查 NTFS 8.3 文件名(#4505)
If Str.RegexCheck(".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
Next
Return ""
End Function
@@ -92,7 +92,7 @@ Public Class CrashAnalyzer
'尝试视作压缩包解压
Try
Dim Info = FileUtils.GetInfo(FilePath)
If Info.Exists AndAlso Info.Length > 0 AndAlso Not FilePath.EndsWithF(".jar", True) Then
If Info.Exists AndAlso Info.Length > 0 AndAlso Not PathUtils.GetExtension(FilePath) = "jar" Then
FileUtils.ExtractToDirectory(FilePath, TempFolder & "Temp\")
Logger.Info($"已解压导入的日志文件:{FilePath}")
GoTo Extracted
@@ -163,9 +163,9 @@ Extracted:
Else
TargetType = AnalyzeFileType.ExtraLogFile
End If
ElseIf MatchName.EndsWithF(".log", True) Then
ElseIf PathUtils.GetExtension(MatchName) = "log" Then
TargetType = AnalyzeFileType.ExtraLogFile
ElseIf MatchName.EndsWithF(".txt", True) Then
ElseIf PathUtils.GetExtension(MatchName) = "txt" Then
TargetType = AnalyzeFileType.ExtraReportFile
Else
Logger.Info($"{MatchName} 分类为 Ignore")
@@ -514,7 +514,6 @@ Done:
If LogMc.Contains("signer information does not match signer information of other classes in the same package") Then AppendReason(CrashReason.文件或内容校验失败, If(LogMc.RegexSeek("(?<=class "")[^']+(?=""'s signer information)"), "").TrimEnd(vbCrLf))
If LogMc.Contains("Maybe try a lower resolution resourcepack?") Then AppendReason(CrashReason.材质过大或显卡配置不足)
If LogMc.Contains("java.lang.NoSuchMethodError: net.minecraft.world.server.ChunkManager$ProxyTicketManager.shouldForceTicks(J)Z") AndAlso LogMc.Contains("OptiFine") Then AppendReason(CrashReason.OptiFine导致无法加载世界)
If LogMc.Contains("Unsupported class file major version") Then AppendReason(CrashReason.Java版本不兼容)
If LogMc.Contains("com.electronwill.nightconfig.core.io.ParsingException: Not enough data available") AndAlso Not CrashReasons.ContainsKey(CrashReason.Mod配置文件导致游戏崩溃) Then AppendReason(CrashReason.NightConfig的Bug)
If LogMc.Contains("Cannot find launch target fmlclient, unable to launch") Then AppendReason(CrashReason.Forge安装不完整)
If LogMc.Contains("Invalid paths argument, contained no existing paths") AndAlso LogMc.Contains("libraries\net\minecraftforge\fmlcore") Then AppendReason(CrashReason.Forge安装不完整)
@@ -522,7 +521,9 @@ Done:
If LogMc.Contains("has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to") Then AppendReason(CrashReason.Mod需要Java11)
If LogMc.Contains("java.lang.RuntimeException: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual") Then AppendReason(CrashReason.Mod需要Java11)
If LogMc.Contains("java.lang.IllegalArgumentException: The requested compatibility level JAVA_11 could not be set. Level is not supported by the active JRE or ASM version") Then AppendReason(CrashReason.Mod需要Java11)
If LogMc.Contains("Unsupported class file major version") Then AppendReason(CrashReason.Java版本不兼容)
If LogMc.Contains("Unsupported major.minor version") Then AppendReason(CrashReason.Java版本不兼容)
If LogMc.Contains("Level is not supported by the active JRE or ASM version") Then AppendReason(CrashReason.Java版本不兼容)
If LogMc.Contains("Invalid maximum heap size") Then AppendReason(CrashReason.使用32位Java导致JVM无法分配足够多的内存)
If LogMc.Contains("Could not reserve enough space") Then
If LogMc.Contains("for 1048576KB object heap") Then
@@ -65,7 +65,7 @@ Public Module ModDownload
#Region "下载资源文件"
If ShouldIgnoreFileCheck(Instance) Then
Logger.Info("已跳过所有 Assets 检查")
Logger.Warn("设置要求跳过所有 Assets 检查,文件可能不是最新的", LogBehavior.None)
Else
Dim LoadersAssets As New List(Of LoaderBase)
'获取资源文件索引地址
@@ -78,13 +78,23 @@ Public Module ModDownload
If IndexFile Is Nothing Then
Task.Output = New List(Of NetFile)
Logger.Warn($"未找到版本 {Instance.Name} 的合适的资源索引下载地址,游戏 assets 可能缺失")
ElseIf DownloadAssetIndexInBackground Then
Return
End If
Dim IndexBackgroundFile = PathTemp & "Cache\" & IndexFile.LocalName & "_background"
If FileUtils.Exists(IndexBackgroundFile) AndAlso
FileUtils.GetInfo(IndexBackgroundFile).LastWriteTime.Date > FileUtils.GetInfo(IndexFile.LocalPath).LastWriteTime.Date Then
Logger.Info($"发现后台更新的资源索引缓存文件:{IndexBackgroundFile}")
FileUtils.Move(IndexBackgroundFile, IndexFile.LocalPath)
End If
If DownloadAssetIndexInBackground Then
'允许后台更新索引
If IndexFile.Check.Check(IndexFile.LocalPath) Is Nothing Then
Task.Output = New List(Of NetFile)
Else
Task.Output = New List(Of NetFile) From {IndexFile}
End If
Else '强制要求重新下载
Else
'强制要求重新下载
RealAddressMain = IndexFile.LocalPath
TempAddressMain = PathTemp & "Cache\" & IndexFile.LocalName
IndexFile.LocalPath = TempAddressMain
@@ -114,28 +124,24 @@ Public Module ModDownload
Dim LoadersAssetsUpdate As New List(Of LoaderBase)
LoadersAssetsUpdate.Add(New LoaderTask(Of String, List(Of NetFile))("后台分析资源文件索引地址",
Sub(Task As LoaderTask(Of String, List(Of NetFile)))
Dim BackAssetsFile As NetFile = DlClientAssetIndexGet(Instance)
If BackAssetsFile Is Nothing Then
Dim IndexFile As NetFile = DlClientAssetIndexGet(Instance)
If IndexFile Is Nothing Then
Logger.Warn($"未找到版本 {Instance.Name} 的合适的资源索引下载地址,游戏 assets 可能缺失")
Task.Cancel()
Throw New OperationCanceledException
End If
RealAddressBg = BackAssetsFile.LocalPath
TempAddressBg = PathTemp & "Cache\" & BackAssetsFile.LocalName & "_background"
BackAssetsFile.LocalPath = TempAddressBg
Task.Output = New List(Of NetFile) From {BackAssetsFile}
RealAddressBg = IndexFile.LocalPath
TempAddressBg = PathTemp & "Cache\" & IndexFile.LocalName & "_background"
IndexFile.LocalPath = TempAddressBg
Task.Output = New List(Of NetFile) From {IndexFile}
'检查是否需要更新:每天只更新一次
If FileUtils.Exists(RealAddressBg) AndAlso Math.Abs((FileUtils.GetInfo(RealAddressBg).LastWriteTime.Date - Now.Date).TotalDays) < 1 Then
Logger.Info("无需更新资源文件索引,取消")
Dim LastUpdate As Date = FileUtils.GetInfo(RealAddressBg).LastWriteTime.Date
If FileUtils.Exists(RealAddressBg) AndAlso Math.Abs((LastUpdate - Now.Date).TotalDays) < 1 Then
Logger.Info($"无需更新资源文件索引,上次更新于 {LastUpdate}")
Task.Cancel()
End If
End Sub))
LoadersAssetsUpdate.Add(New LoaderDownload("后台下载资源文件索引", New List(Of NetFile)))
LoadersAssetsUpdate.Add(New LoaderTask(Of List(Of NetFile), String)("后台复制资源文件索引",
Sub(Task As LoaderTask(Of List(Of NetFile), String))
FileUtils.Move(TempAddressBg, RealAddressBg)
McLaunchLog("后台更新资源文件索引成功:" & TempAddressBg)
End Sub))
Dim Updater As New LoaderCombo(Of String)("后台更新资源文件索引", LoadersAssetsUpdate)
Logger.Info("开始后台检查资源文件索引")
Updater.Start()
@@ -236,8 +242,6 @@ Public Module ModDownload
DlPreferMojang = DeltaTime < 4000
Logger.Info($"Mojang 官方源加载耗时:{DeltaTime}ms,{If(DlPreferMojang, "可优先使用官方源", "不优先使用官方源")}")
End If
'添加 PCL 特供项
If FileUtils.Exists(PathTemp & "Cache\download.json") Then Versions.Merge(FileUtils.ReadAsJson(PathTemp & "Cache\download.json"))
'返回
Loader.Output = New DlClientListResult With {.IsOfficial = True, .SourceName = "Mojang 官方源", .Value = Json}
'MC 更新提示
@@ -432,7 +436,7 @@ Public Module ModDownload
.FileName = If(Name(i).ContainsIgnoreCase("pre"), "preview_", "") & "OptiFine_" & Name(i).Replace(" ", "_") & ".jar",
.RequiredForgeVersion = Forge(i).Replace("Forge ", "").Replace("#", "")}
If Entry.RequiredForgeVersion.Contains("N/A") Then Entry.RequiredForgeVersion = Nothing
Entry.InstanceName = Entry.Inherit & "-OptiFine_" & Name(i).ToString.Replace(" ", "_").Replace(Entry.Inherit & "_", "")
Entry.InstanceName = Entry.Inherit & "-OptiFine_" & Name(i).AfterFirst(" ").Replace(" ", "_")
Versions.Add(Entry)
Next
Loader.Output = New DlOptiFineListResult With {.IsOfficial = True, .SourceName = "OptiFine 官方源", .Value = Versions}
@@ -575,7 +579,7 @@ Public Module ModDownload
If Version <> other.Version Then
Return Version.CompareTo(other.Version)
Else
Return CompareVersion(VersionName, other.VersionName)
Return McUtils.CompareVersion(VersionName, other.VersionName)
End If
End Function
End Class
@@ -1193,15 +1197,15 @@ Public Module ModDownload
Urls.Add((Url, If(Url.Contains("modrinth"), 20, 10)))
Urls.Add((Url, 30))
End If
Dim Exs As String = ""
Dim Errors As New List(Of Exception)
For Each Source In Urls
Try
Return NetRequestByClient(Source.Url, Method, Content, ContentType, Timeout:=Source.Timeout * 1000, Encoding:=Encoding.UTF8, RequireJson:=True).DeserializeJson()
Catch ex As Exception
Exs += ex.Message + vbCrLf
Errors.Add(ex)
End Try
Next
Throw New Exception(Exs)
Throw New AggregateException(Errors)
End Function
#End Region
@@ -18,7 +18,7 @@ Public Module ModJava
'强制选择的 Java
Dim SelectedJavaSetting = Settings.Get(Of String)("LaunchArgumentJavaSelect")
Dim SelectedJava = If(SelectedJavaSetting.StartsWithF("{"), SelectedJavaSetting.DeserializeJson(Of JObject)()("Path").ToString, "")
Dim Match = OldJavaList.FirstOrDefault(Function(j) j.Folder.Equals(SelectedJava, StringComparison.OrdinalIgnoreCase))
Dim Match = OldJavaList.FirstOrDefault(Function(j) PathUtils.IsSame(j.Folder, SelectedJava))
If Match IsNot Nothing Then
OldJavaList.Remove(Match)
OldJavaList.Insert(0, Match)
@@ -78,7 +78,7 @@ Public Module ModJava
Return
End If
'不再移除它
Configs.JavaRemovedList.Edit(Sub(ByRef List) List.RemoveIf(Function(f) f.Equals(NewJava.Folder, StringComparison.OrdinalIgnoreCase)))
Configs.JavaRemovedList.Edit(Sub(ByRef List) List.RemoveIf(Function(f) PathUtils.IsSame(f, NewJava.Folder)))
'加入列表顶部
Configs.JavaList.Edit(
Sub(ByRef List)
@@ -101,11 +101,12 @@ Public Module ModJava
End Sub
''' <summary>
''' 手动触发搜索 Java。
''' 手动触发搜索 Java,强制检查所有 Java。
''' </summary>
Public Sub ManuallySearchJava(Optional CancellationToken As CancellationToken = Nothing)
RunInThread(
Sub()
Configs.JavaList.Get().ForAll(Sub(java) java.InvalidateChecked()) '重置已检查的状态
JavaListRefreshWorker.Start(cancellationToken:=CancellationToken)
JavaListRefreshWorker.WaitIfRunningAsync(cancellationToken:=CancellationToken).Run() 'worker 开始/结束时会自动重载 UI
If Configs.JavaList.Get().Any Then
@@ -142,20 +143,23 @@ Public Module ModJava
Dim AddConstraint =
Sub(Constraint As ValueRange(Of Version))
JavaRange = JavaRange?.Intersect(Constraint)
If JavaRange.IsEmpty Then Logger.Warn($"Java 版本要求冲突(当前追加的要求为 {Constraint},版本为 {Instance.VersionDisplayName})")
If JavaRange.IsEmpty Then
Logger.Warn($"Java 版本要求冲突,若启动失败请在版本设置手动指定 Java:当前追加的要求为 {Constraint},游戏版本为 {Instance.VersionDisplayName}", LogBehavior.Toast)
JavaRange = Constraint
End If
End Sub
'原版 26-:通过版本号判断
If (Not Instance.Version.Vaild AndAlso Instance.ReleaseTime >= New Date(2024, 4, 2)) OrElse
(Instance.Version.Vaild AndAlso Instance.Version.Vanilla >= New Version(20, 0, 5)) Then
If (Not Instance.Version.Valid AndAlso Instance.ReleaseTime >= New Date(2024, 4, 2)) OrElse
(Instance.Version.Valid AndAlso Instance.Version.Vanilla >= New Version(20, 0, 5)) Then
'1.20.5+(24w14a+):至少 Java 21
AddConstraint(ValueRange(Of Version).AtLeast(New Version(21, 0)))
ElseIf (Not Instance.Version.Vaild AndAlso Instance.ReleaseTime >= New Date(2021, 11, 16)) OrElse
(Instance.Version.Vaild AndAlso Instance.Version.Vanilla.Major >= 18) Then
ElseIf (Not Instance.Version.Valid AndAlso Instance.ReleaseTime >= New Date(2021, 11, 16)) OrElse
(Instance.Version.Valid AndAlso Instance.Version.Vanilla.Major >= 18) Then
'1.18 pre2+:至少 Java 17
AddConstraint(ValueRange(Of Version).AtLeast(New Version(17, 0)))
ElseIf (Not Instance.Version.Vaild AndAlso Instance.ReleaseTime >= New Date(2021, 5, 11)) OrElse
(Instance.Version.Vaild AndAlso Instance.Version.Vanilla.Major >= 17) Then
ElseIf (Not Instance.Version.Valid AndAlso Instance.ReleaseTime >= New Date(2021, 5, 11)) OrElse
(Instance.Version.Valid AndAlso Instance.Version.Vanilla.Major >= 17) Then
'1.17+ (21w19a+):至少 Java 16
AddConstraint(ValueRange(Of Version).AtLeast(New Version(16, 0)))
ElseIf Instance.ReleaseTime.Year >= 2017 Then 'Minecraft 1.12 与 1.11 的分界线正好是 2017 年,太棒了
@@ -181,7 +185,7 @@ Public Module ModJava
End If
'OptiFine 检测
If Instance.Version.HasOptiFine AndAlso Instance.Version.Vaild Then '不管非标准版本
If Instance.Version.HasOptiFine AndAlso Instance.Version.Valid Then '不管非标准版本
If Instance.Version.Vanilla.Major < 7 Then
'<1.7:至多 Java 8
AddConstraint(ValueRange(Of Version).LessThan(New Version(9, 0)))
@@ -195,11 +199,11 @@ Public Module ModJava
End If
'Forge 检测(部分测试结果见 #8432)
If Instance.Version.HasForge Then
If Instance.Version.ModLoaders.HasFlagF(ModLoaders.Forge) Then
If Instance.Version.Vanilla >= New Version(6, 0, 1) AndAlso Instance.Version.Vanilla <= New Version(7, 0, 2) Then
'1.6.1 - 1.7.2:必须 Java 7
AddConstraint(ValueRange(Of Version).ClosedOpen(New Version(7, 0), New Version(8, 0)))
ElseIf Instance.Version.Vanilla.Major <= 12 OrElse Not Instance.Version.Vaild Then '非标准版本
ElseIf Instance.Version.Vanilla.Major <= 12 OrElse Not Instance.Version.Valid Then '非标准版本
'<=1.12:Java 8
AddConstraint(ValueRange(Of Version).LessThan(New Version(9, 0)))
ElseIf Instance.Version.Vanilla.Major <= 14 Then
@@ -208,38 +212,38 @@ Public Module ModJava
ElseIf Instance.Version.Vanilla.Major = 15 Then
'1.15:Java 8 - 15
AddConstraint(ValueRange(Of Version).ClosedOpen(New Version(8, 0), New Version(16, 0)))
ElseIf CompareVersionGE(Instance.Version.Forge, "34.0.0") AndAlso CompareVersionGE("36.2.25", Instance.Version.Forge) Then
ElseIf McUtils.CompareVersionGE(Instance.Version.ModLoaderVersions(ModLoaders.Forge), "34.0.0") AndAlso McUtils.CompareVersionGE("36.2.25", Instance.Version.ModLoaderVersions(ModLoaders.Forge)) Then
'1.16.3~5,Forge 34.0.0 ~ 36.2.25:最高 Java 8u320
AddConstraint(ValueRange(Of Version).AtMost(New Version(8, 0, 320)))
ElseIf CompareVersionGE(Instance.Version.Forge, "36.2.26") AndAlso CompareVersionGE("36.999999.999999", Instance.Version.Forge) Then
ElseIf McUtils.CompareVersionGE(Instance.Version.ModLoaderVersions(ModLoaders.Forge), "36.2.26") AndAlso McUtils.CompareVersionGE("36.999999.999999", Instance.Version.ModLoaderVersions(ModLoaders.Forge)) Then
'1.16.5,Forge 36.2.26+,<37.0.0:最高 Java 23
AddConstraint(ValueRange(Of Version).LessThan(New Version(24, 0)))
ElseIf CompareVersionGE(Instance.Version.Forge, "37.0.0") AndAlso CompareVersionGE("37.0.79", Instance.Version.Forge) Then
ElseIf McUtils.CompareVersionGE(Instance.Version.ModLoaderVersions(ModLoaders.Forge), "37.0.0") AndAlso McUtils.CompareVersionGE("37.0.79", Instance.Version.ModLoaderVersions(ModLoaders.Forge)) Then
'1.17.1,Forge 37.0.0 ~ 37.0.79:最高 Java 16
AddConstraint(ValueRange(Of Version).LessThan(New Version(17, 0)))
ElseIf Instance.Version.Vanilla.Major = 18 AndAlso Instance.Version.HasOptiFine Then '#305
'1.18:若安装了 OptiFine,最高 Java 18
AddConstraint(ValueRange(Of Version).LessThan(New Version(19, 0)))
ElseIf CompareVersionGE(Instance.Version.Forge, "45.0.21") AndAlso CompareVersionGE("45.0.65", Instance.Version.Forge) Then
ElseIf McUtils.CompareVersionGE(Instance.Version.ModLoaderVersions(ModLoaders.Forge), "45.0.21") AndAlso McUtils.CompareVersionGE("45.0.65", Instance.Version.ModLoaderVersions(ModLoaders.Forge)) Then
'1.19.4,Forge 45.0.21 ~ 45.0.65:最高 Java 19
AddConstraint(ValueRange(Of Version).LessThan(New Version(20, 0)))
ElseIf CompareVersionGE(Instance.Version.Forge, "45.0.66") AndAlso CompareVersionGE("47.4.8", Instance.Version.Forge) Then
ElseIf McUtils.CompareVersionGE(Instance.Version.ModLoaderVersions(ModLoaders.Forge), "45.0.66") AndAlso McUtils.CompareVersionGE("47.4.8", Instance.Version.ModLoaderVersions(ModLoaders.Forge)) Then
'1.19.4~1.20.1,Forge 45.0.66 ~ 47.4.8:最高 Java 21
AddConstraint(ValueRange(Of Version).LessThan(New Version(22, 0)))
End If
End If
'NeoForge 检测
If Instance.Version.HasNeoForge Then
If Instance.Version.ModLoaders.HasFlagF(ModLoaders.NeoForge) Then
If Instance.Version.Vanilla = New Version(20, 0, 1) OrElse
(CompareVersionGE("20.2.62-beta", Instance.Version.NeoForge) AndAlso Not Instance.Version.NeoForge.Contains("25w14craftmine")) Then
(McUtils.CompareVersionGE("20.2.62-beta", Instance.Version.ModLoaderVersions(ModLoaders.NeoForge)) AndAlso Not Instance.Version.ModLoaderVersions(ModLoaders.NeoForge).Contains("25w14craftmine")) Then
'1.20.1,以及 1.20.2 的 20.2.62-beta 之前:最高 Java 21
AddConstraint(ValueRange(Of Version).LessThan(New Version(22, 0)))
End If
End If
'Fabric 检测
If Instance.Version.HasFabric AndAlso Instance.Version.Vaild Then '不管非标准版本
If Instance.Version.ModLoaders.HasFlagF(ModLoaders.Fabric) AndAlso Instance.Version.Valid Then '不管非标准版本
If Instance.Version.Vanilla.Major >= 15 AndAlso Instance.Version.Vanilla.Major <= 16 Then
'1.15 - 1.16:Java 8+
AddConstraint(ValueRange(Of Version).AtLeast(New Version(8, 0)))
@@ -247,10 +251,14 @@ Public Module ModJava
'1.18+:Java 17+
AddConstraint(ValueRange(Of Version).AtLeast(New Version(17, 0)))
End If
If Instance.Version.ModLoaderVersions(ModLoaders.Fabric) <> "未知版本" AndAlso McUtils.CompareVersion(Instance.Version.ModLoaderVersions(ModLoaders.Fabric), "0.17.0") < 0 Then
'Fabric Loader 0.16.x 及更早版本的 Mixin/ASM 不兼容 Java 25
AddConstraint(ValueRange(Of Version).LessThan(New Version(25, 0)))
End If
End If
'LiteLoader 检测
If Instance.Version.HasLiteLoader AndAlso Instance.Version.Vaild Then '不管非标准版本
If Instance.Version.ModLoaders.HasFlagF(ModLoaders.LiteLoader) AndAlso Instance.Version.Valid Then '不管非标准版本
'最高 Java 8
AddConstraint(ValueRange(Of Version).LessThan(New Version(9, 0)))
End If
@@ -294,7 +302,7 @@ Public Module ModJava
Case 3 '=============================== 强制指定特定 Java ===============================
Dim ChosenJava = Configs.InstanceForcedJava.Get(Instance.Config)
Dim ChosenJava = Configs.JavaForced.Get(Instance.Config)
Logger.Info($"版本设置中强制指定的 Java:{Instance.PathVersion} → {ChosenJava}")
If ChosenJava Is Nothing Then
If TryFixOrHint Then
@@ -305,7 +313,7 @@ Public Module ModJava
p?.Skip() : Return Nothing '<==== 设置中未指定 Java
End If
If Not Configs.JavaList.Get().Contains(ChosenJava) Then
Configs.InstanceForcedJava.Reset(Instance.Config)
Configs.JavaForced.Reset(Instance.Config)
UpdateJavaLists()
If TryFixOrHint Then
SwitchToInstanceSetup(Instance)
@@ -314,7 +322,7 @@ Public Module ModJava
p?.Skip() : Return Nothing '<==== 设置中指定的 Java 不在列表中
End If
If Not ChosenJava.CheckAsync(c).Run() Then
Configs.InstanceForcedJava.Reset(Instance.Config)
Configs.JavaForced.Reset(Instance.Config)
UpdateJavaLists()
If TryFixOrHint Then
SwitchToInstanceSetup(Instance)
@@ -365,14 +373,46 @@ Public Module ModJava
'电脑中没有合适的 Java,获取 Mojang 提供的 Java 下载列表
Logger.Warn($"电脑中没有合适的 Java,开始获取 Mojang 提供的 Java 下载列表")
Dim Components = DownloadJavaList()
c.ThrowIfCancellationRequested()
p?.Set(0.4)
Static DownloadListWorker As New RedoableWorker(Of List(Of JavaDownloadInfo))(
Function(cancellationToken)
'下载 Mojang 提供的 Java 列表
Dim IndexFileStr As String = NetRequestByLoader(DlVersionListOrder(
{"https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"},
{"https://bmclapi2.bangbang93.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"}
), IsJson:=True)
cancellationToken?.ThrowIfCancellationRequested()
'解析 Java 列表
Dim Results = New List(Of JavaDownloadInfo)
For Each Prop As JProperty In IndexFileStr.DeserializeJson(Of JObject)()("windows-x64")
cancellationToken?.ThrowIfCancellationRequested()
If Prop.Name = "minecraft-java-exe" Then Continue For '不是完整 Java
Try
Dim Entry = Prop.Value.FirstOrDefault
If Entry Is Nothing Then Continue For
Dim JavaName = Entry("version")("name").ToString '可能的名称格式:"12"、"8u51-cacert462b08"、"17.0.8.1"、"17.0.1.12.1"、"21.0.7"
If JavaName.Contains(".") Then
Dim Parts As String() = JavaName.Split(".")
Results.Add(New JavaDownloadInfo With {.ComponentName = Prop.Name, .Java = Entry, .JavaVersion = New Version(Val(Parts(0)), Val(Parts(1)), If(Parts.Length >= 3, Val(Parts(2)), 0), If(Parts.Length >= 4, Val(Parts(3)), 0))})
ElseIf JavaName.StartsWithF("8u51-") Then
Results.Add(New JavaDownloadInfo With {.ComponentName = Prop.Name, .Java = Entry, .JavaVersion = New Version(8, 0, 51, 0)})
Else
Results.Add(New JavaDownloadInfo With {.ComponentName = Prop.Name, .Java = Entry, .JavaVersion = New Version(Val(JavaName.BeforeFirstOfAny({"u", "."})), 0, 0, 0)})
End If
Catch ex As Exception
Logger.Warn(ex, $"解析 Mojang 提供的 Java 列表项失败,已跳过({Prop.Name})")
End Try
Next
Return Results
End Function)
If Not DownloadListWorker.HasSucceeded Then DownloadListWorker.Start(c, p?.SplitTo(0.4))
DownloadListWorker.WaitIfRunning(cancellationToken:=c)
p?.Set(0.4, skiped:=True)
Dim Components = DownloadListWorker.LastResult
'确定要下载的目标 Java
Dim JavaToDownload As JavaDownloadInfo = If(
Components.FirstOrDefault(Function(Comp) Comp.Java("version")("name").ToString = RecommendedComponent), '优先选择指定的 Component
Components.FirstOrDefault(Function(Comp) Range.Contains(Comp.JavaVersion)))
Dim JavaToDownload As JavaDownloadInfo = Components.FirstOrDefault(
Function(Comp) Comp.Java("version")("name").ToString = RecommendedComponent, '优先选择指定的 Component
Function(Comp) Range.Contains(Comp.JavaVersion))
If JavaToDownload Is Nothing Then
Logger.Warn($"Mojang 提供的 Java 列表中没有合适的 Java,无法自动下载")
'提示需要手动下载 Java
@@ -443,7 +483,7 @@ Public Module ModJava
''' 从老版本 PCL 迁移版本独立的 Java 设置。
''' </summary>
Private Sub MigrateInstanceJavaSettings(Instance As McInstance, Optional c As CancellationToken = Nothing, Optional p As ProgressProvider = Nothing)
If Configs.InstanceMigratedJava.Get(Instance.Config) Then p?.Skip() : Return
If Configs.JavaMigrated.Get(Instance.Config) Then p?.Skip() : Return
If FrmInstanceSetup IsNot Nothing Then RunInUiWait(Sub() FrmInstanceSetup.ComboArgumentJava.IsEnabled = False)
Try
Logger.Info($"正在尝试迁移版本独立的 Java 设置({Instance.Name})")
@@ -462,10 +502,10 @@ Public Module ModJava
Configs.JavaList.Edit(Sub(ByRef List) List.Add(ForcedJava)) '加入列表底部
Logger.Info($"已将强制指定的 Java 加入 Java 列表底部:{ForcedJava}")
End If
Configs.InstanceForcedJava.Set(ForcedJava, Instance.Config)
Configs.JavaForced.Set(ForcedJava, Instance.Config)
Settings.Set("VersionArgumentJavaV2", 3, Instance)
UpdateJavaLists()
Configs.InstanceMigratedJava.Set(True, Instance.Config) '完成
Configs.JavaMigrated.Set(True, Instance.Config) '完成
p?.Finish() : Return
NoForcedJava:
Dim JavaFound = JavaUtils.SearchFoldersAsync(True, {Instance.PathVersion}, c, p?.SplitTo(1)).Run()
@@ -478,7 +518,7 @@ NoForcedJava:
Logger.Info("没有需要迁移的内容,自动选择 Java")
Settings.Set("VersionArgumentJavaV2", 0, Instance)
End If
Configs.InstanceMigratedJava.Set(True, Instance.Config) '完成
Configs.JavaMigrated.Set(True, Instance.Config) '完成
Finally
If FrmInstanceSetup IsNot Nothing Then RunInUiWait(
Sub()
@@ -499,44 +539,6 @@ NoForcedJava:
Public Property JavaVersion As Version
End Class
''' <summary>
''' 下载 Mojang 提供的 Java 列表。
''' <para/> 若本次打开程序已经下载过,则会直接返回缓存的结果。
''' </summary>
Private Function DownloadJavaList() As List(Of JavaDownloadInfo)
'TODO: 写一个更简单的 Lazy,允许重置缓存
Static Components As New Lazy(Of List(Of JavaDownloadInfo))(
Function()
'下载 Mojang 提供的 Java 列表
Dim IndexFileStr As String = NetRequestByLoader(DlVersionListOrder(
{"https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"},
{"https://bmclapi2.bangbang93.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"}
), IsJson:=True)
'解析 Java 列表
Dim Results = New List(Of JavaDownloadInfo)
For Each Prop As JProperty In IndexFileStr.DeserializeJson(Of JObject)()("windows-x64")
If Prop.Name = "minecraft-java-exe" Then Continue For '不是完整 Java
Try
Dim Entry = Prop.Value.FirstOrDefault
If Entry Is Nothing Then Continue For
Dim JavaName = Entry("version")("name").ToString '可能的名称格式:"12"、"8u51-cacert462b08"、"17.0.8.1"、"17.0.1.12.1"、"21.0.7"
If JavaName.Contains(".") Then
Dim Parts As String() = JavaName.Split(".")
Results.Add(New JavaDownloadInfo With {.ComponentName = Prop.Name, .Java = Entry, .JavaVersion = New Version(Val(Parts(0)), Val(Parts(1)), If(Parts.Length >= 3, Val(Parts(2)), 0), If(Parts.Length >= 4, Val(Parts(3)), 0))})
ElseIf JavaName.StartsWithF("8u51-") Then
Results.Add(New JavaDownloadInfo With {.ComponentName = Prop.Name, .Java = Entry, .JavaVersion = New Version(8, 0, 51, 0)})
Else
Results.Add(New JavaDownloadInfo With {.ComponentName = Prop.Name, .Java = Entry, .JavaVersion = New Version(Val(JavaName.BeforeFirstOfAny({"u", "."})), 0, 0, 0)})
End If
Catch ex As Exception
Logger.Warn(ex, $"解析 Mojang 提供的 Java 列表项失败,已跳过({Prop.Name})")
End Try
Next
Return Results
End Function)
Return Components.Value
End Function
''' <summary>
''' 构建下载指定 Java 的加载器。
''' </summary>
@@ -48,7 +48,7 @@ Public Module ModLaunch
End Try
'检查版本
CurrentLaunchOptions.Instance.Load()
If CurrentLaunchOptions.Instance.State = McInstanceState.Error Then
If CurrentLaunchOptions.Instance.State = McInstance.McInstanceState.Error Then
Hint($"无法启动 {CurrentLaunchOptions.Instance.Name}:{CurrentLaunchOptions.Instance.Info}", HintType.Red)
Return False
End If
@@ -57,6 +57,10 @@ Public Module ModLaunch
FrmLaunchLeft.RefreshButtonsUI()
FrmLaunchLeft.RefreshPage(False, False)
End If
If McInstanceSelected IsNot Nothing AndAlso McInstanceLock.IsLocked(McInstanceSelected) Then
Hint($"版本 {McInstanceSelected.Name} 正在被修改,请稍后再启动!", HintType.Red)
Return False
End If
FrmMain.AprilGiveup()
'禁止进入版本选择页面(否则就可以在启动中切换 McInstanceSelected 了)
FrmMain.PageStack = FrmMain.PageStack.Where(Function(p) p.Page <> FormMain.PageType.InstanceSelect).ToList
@@ -70,7 +74,12 @@ Public Module ModLaunch
''' </summary>
Public Sub McLaunchLog(Text As String, Optional Level As LogLevel = LogLevel.Info)
Text = FilterUserName(FilterAccessToken(Text, "*"), "*")
If ModeDebug Then RunInUi(Sub() FrmLaunchRight.LabLog.Text += $"{vbCrLf}[{Date.Now:HH':'mm':'ss'.'fff}] {Text}")
If ModeDebug Then
RunInUi(
Sub()
FrmLaunchRight.LabLog.AppendText($"[{Date.Now:HH':'mm':'ss'.'fff}] {Text}{vbCrLf}")
End Sub)
End If
Logger.Log(Text, Level)
End Sub
@@ -230,7 +239,7 @@ NextInner:
'检查版本
If McInstanceSelected Is Nothing Then Throw New Exception("未选择 Minecraft 版本!")
McInstanceSelected.Load()
If McInstanceSelected.State = McInstanceState.Error Then Throw New Exception("Minecraft 存在问题:" & McInstanceSelected.Info)
If McInstanceSelected.State = McInstance.McInstanceState.Error Then Throw New Exception("Minecraft 存在问题:" & McInstanceSelected.Info)
'检查输入信息
Dim CheckResult As String = ""
RunInUiWait(Sub() CheckResult = McLoginAble(McLoginInput()))
@@ -463,7 +472,8 @@ NextInner:
End Function
'登录主模块加载器
Public McLoginLoader As New LoaderTask(Of McLoginData, McLoginResult)("登录", AddressOf McLoginStart, AddressOf McLoginInput, ThreadPriority.BelowNormal) With {.ReloadTimeout = 1, .ProgressWeight = 15, .Block = False}
Public McLoginLoader As New LoaderTask(Of McLoginData, McLoginResult)("登录", AddressOf McLoginStart, AddressOf McLoginInput, ThreadPriority.BelowNormal) With
{.ReloadTimeout = 1, .ProgressWeight = 15, .Block = False}
Public Function McLoginInput() As McLoginData
Dim LoginData As McLoginData = Nothing
Dim LoginType = Settings.Get(Of McLoginType)("LoginType")
@@ -537,7 +547,7 @@ NextInner:
'检查是否已经登录完成
Dim ExpiresAt = Settings.Get(Of Long)("CacheMsV2Expires")
If Not Data.IsForceRestarting AndAlso '不要求强行重启
ExpiresAt > 0 AndAlso ExpiresAt > GetUnixTimestampUtc() AndAlso 'AccessToken 尚未过期
ExpiresAt > 0 AndAlso ExpiresAt > ToUnixTimestampUtc(Date.UtcNow) AndAlso 'AccessToken 尚未过期
Input.UserName = Settings.Get(Of String)("CacheMsV2Name") Then
Data.Output = New McLoginResult With {
.Name = Input.UserName, .Type = "Microsoft",
@@ -1032,7 +1042,7 @@ Retry:
Dim ResultJson As JObject = Result.DeserializeJson()
Return (
ResultJson("access_token").ToString,
ResultJson("expires_in").ToObject(Of Integer) + GetUnixTimestampUtc() - 1200) '提前 20 分钟视作过期
ResultJson("expires_in").ToObject(Of Integer) + GetUnixTimestampUtc() - 3600) '提前 1 小时视作过期
End Function
'微软登录步骤 5:验证微软账号是否持有 MC,这也会刷新 XGP
Private Sub MsLoginStep5(AccessToken As String)
@@ -1175,6 +1185,7 @@ Retry:
#Region "启动参数"
Private McLaunchArgument As String
Private McLaunchEncoding As Encoding
'主方法
Private Sub McLaunchArgumentMain(Loader As LoaderTask(Of String, Integer))
@@ -1296,8 +1307,8 @@ NextInstance:
'LUA
Dim UseLUA As Boolean =
Not Settings.Get(Of Boolean)("LaunchAdvanceDisableLUA") AndAlso Not Settings.Get(Of Boolean)("VersionAdvanceDisableLUA", McInstanceSelected) AndAlso
McLibListGet(McInstanceSelected, False).Any(Function(e) e.OriginalName = "org.lwjgl:lwjgl:3.4.1")
If UseLUA Then Args.Add($"-javaagent:""{ExtractPatch("LUA")}""")
McLibListGet(McInstanceSelected, False).Select(Function(e) e.OriginalName).Contains("org.lwjgl:lwjgl:3.4.1")
If UseLUA Then Args.Add($"-javaagent:""{LaunchUtils.ExtractPatch("Launch\LwjglUnsafeAgent.jar", PathPure.Value)}""")
McLaunchLog("使用 LUA:" & UseLUA)
#Region "内存管理"
@@ -1361,14 +1372,29 @@ NextInstance:
'Log4j 漏洞防御参数
Args.Add("-Dlog4j2.formatMsgNoLookups=true")
'编码(#4700、#5892、#5909)
If McLaunchJavaSelected.Version.Major > 8 AndAlso Not Args.Any(Function(a) a.StartsWithF("-Dstdout.encoding=")) Then Args.Add("-Dstdout.encoding=UTF-8")
If McLaunchJavaSelected.Version.Major > 8 AndAlso Not Args.Any(Function(a) a.StartsWithF("-Dstderr.encoding=")) Then Args.Add("-Dstderr.encoding=UTF-8")
If McLaunchJavaSelected.Version.Major >= 18 AndAlso Not Args.Any(Function(a) a.StartsWithF("-Dfile.encoding=")) Then Args.Add("-Dfile.encoding=COMPAT") 'Dfile.encoding 需要放在 Dstdout.encoding 后面(#6934)
'编码(#4700、#5892、#5909、#6934、#8950、#9241)
Dim UseCompatEncoding = McLaunchJavaSelected.Version.Major >= 18
Dim EncodingName = If(UseCompatEncoding,
McLaunchJavaSelected.NativeEncodingName, 'Java 18+ 会添加 COMPAT,因此 Java 会回退到 native.encoding
McLaunchJavaSelected.FileEncodingName) 'Java 17- 直接使用 file.encoding
Try
McLaunchEncoding = Encoding.GetEncoding(If(EncodingName.Equals("GBK", StringComparison.OrdinalIgnoreCase), "GB18030", EncodingName))
Catch ex As Exception
Dim FallbackEncoding = Encoding.GetEncoding(Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage) '老版本 Java 的编码选择行为
McLaunchLog($"无法识别 Java 编码 {If(EncodingName, "Nothing")},已回退为 {FallbackEncoding.WebName}", LogLevel.Warn)
McLaunchEncoding = FallbackEncoding
End Try
McLaunchLog($"使用编码:{McLaunchEncoding.WebName}")
Dim LaunchEncodingName = If(McLaunchEncoding.CodePage = 54936, "GBK", McLaunchEncoding.WebName)
Dim StdoutArgName = If(McLaunchJavaSelected.Version.Major < 19, "sun.stdout.encoding", "stdout.encoding") '老版本 Java 应该设置 sun.std
Dim StderrArgName = If(McLaunchJavaSelected.Version.Major < 19, "sun.stderr.encoding", "stderr.encoding")
If Not Args.Any(Function(a) a.StartsWithF($"-D{StdoutArgName}=")) Then Args.Add($"-D{StdoutArgName}={LaunchEncodingName}")
If Not Args.Any(Function(a) a.StartsWithF($"-D{StderrArgName}=")) Then Args.Add($"-D{StderrArgName}={LaunchEncodingName}")
If UseCompatEncoding AndAlso Not Args.Any(Function(a) a.StartsWithF("-Dfile.encoding=")) Then Args.Add("-Dfile.encoding=COMPAT") '防止老版本 MC 与部分 Mod 不兼容 UTF-8
'为 JLW 添加 -jar,它必须放在最后
If UseJLW Then
Args.Add("-jar") : Args.Add(ExtractPatch("JavaWrapper"))
Args.Add("-jar") : Args.Add(LaunchUtils.ExtractPatch("Launch\JavaWrapper.jar", PathPure.Value))
End If
'再次去重并输出
@@ -1424,7 +1450,8 @@ NextInstance:
Arg &= " " & CustomArg
'把 OptiFineForgeTweaker 放在参数的末尾
If (McInstanceSelected.Version.HasForge OrElse McInstanceSelected.Version.HasLiteLoader) AndAlso McInstanceSelected.Version.HasOptiFine Then
If (McInstanceSelected.Version.ModLoaders.HasFlagF(ModLoaders.Forge) OrElse McInstanceSelected.Version.ModLoaders.HasFlagF(ModLoaders.LiteLoader)) AndAlso
McInstanceSelected.Version.HasOptiFine AndAlso Not McInstanceSelected.Version.OptiFineAsMod Then
If Arg.Contains("--tweakClass optifine.OptiFineForgeTweaker") Then
McLaunchLog("发现正确的 OptiFineForge TweakClass,目前的游戏参数:" & Arg)
Arg = Arg.Replace(" --tweakClass optifine.OptiFineForgeTweaker", "") & " --tweakClass optifine.OptiFineForgeTweaker"
@@ -1479,10 +1506,10 @@ NextInstance:
Private Iterator Function McLaunchArgumentsReplace(Arguments As List(Of String)) As IEnumerable(Of (Key As String, Value As String))
'基础参数
Yield ("${classpath_separator}", ";")
Yield ("${natives_directory}", GetNativesFolder().TrimEnd("\"c))
Yield ("${natives_directory}", PathUtils.RemoveSlashSuffix(GetNativesFolder()))
Yield ("${library_directory}", PathUtils.ToShortPath(McFolderSelected & "libraries"))
Yield ("${libraries_directory}", PathUtils.ToShortPath(McFolderSelected & "libraries"))
Yield ("${pure_directory}", PathPure.TrimEnd("\"c)) '由 PCL 添加,这会允许在分割参数并去重后再替换路径,防止路径中的特殊字符影响参数分割和去重
Yield ("${pure_directory}", PathUtils.RemoveSlashSuffix(PathPure.Value)) '由 PCL 添加,这会允许在分割参数并去重后再替换路径,防止路径中的特殊字符影响参数分割和去重
Yield ("${launcher_name}", "PCL")
Yield ("${launcher_version}", VersionCode)
Yield ("${version_name}", McInstanceSelected.Name)
@@ -1527,9 +1554,8 @@ NextInstance:
Case Else
TargetSize = New Size(854, 480)
End Select
If McInstanceSelected.Version.Vanilla.Major <= 12 AndAlso
McLaunchJavaSelected.Version >= New Version(8, 0, 200) AndAlso McLaunchJavaSelected.Version <= New Version(8, 0, 321) AndAlso
Not McInstanceSelected.Version.HasOptiFine AndAlso Not McInstanceSelected.Version.HasForge Then '修复 1.12.2- 下 JRE 8u200~321 下窗口大小为设置大小的 DPI% 倍(#3463)
If McInstanceSelected.Version.Vanilla.Major <= 12 AndAlso McLaunchJavaSelected.Version >= New Version(8, 0, 200) AndAlso McLaunchJavaSelected.Version <= New Version(8, 0, 321) AndAlso
Not McInstanceSelected.Version.HasOptiFine AndAlso Not McInstanceSelected.Version.ModLoaders.HasFlagF(ModLoaders.Forge) Then '修复 1.12.2- 下 JRE 8u200~321 下窗口大小为设置大小的 DPI% 倍(#3463)
McLaunchLog($"已应用窗口大小过大修复(Java 版本:{McLaunchJavaSelected.Version})")
TargetSize.Width /= DPI / 96
TargetSize.Height /= DPI / 96
@@ -1653,7 +1679,7 @@ NextInstance:
End Try
For Each Entry In Zip.Entries
Dim FileName As String = Entry.FullName
If FileName.EndsWithF(".dll", True) Then
If PathUtils.GetExtension(FileName) = "dll" Then
'实际解压文件的步骤
Dim FilePath As String = Target & FileName
ExistFiles.Add(FilePath)
@@ -1697,8 +1723,9 @@ NextInstance:
''' <summary>
''' 获取 Natives 文件夹路径,以 \ 结尾。
''' </summary>
Private Function GetNativesFolder() As String
Dim Result As String = PathUtils.AddSlashSuffix(PathUtils.ToShortPath(McInstanceSelected.PathVersion)) & McInstanceSelected.Name & "-natives\"
Public Function GetNativesFolder(Optional Instance As McInstance = Nothing) As String
If Instance Is Nothing Then Instance = McInstanceSelected
Dim Result As String = PathUtils.AddSlashSuffix(PathUtils.ToShortPath(Instance.PathVersion)) & Instance.Name & "-natives\"
If Not (IsGBKEncoding OrElse Result.IsAsciiOnly()) Then
Result = Paths.AppData & ".minecraft\bin\natives\"
If Not Result.IsAsciiOnly() Then
@@ -1824,7 +1851,7 @@ NextInstance:
Dim CurrentLang As String = ReadIni(SetupFileAddress, "lang", "none")
Dim RequiredLang As String = If(CurrentLang = "none" OrElse Not DirectoryUtils.Exists(McInstanceSelected.PathIndie & "saves"), '#3844,整合包可能已经自带了 options.txt
If(Settings.Get(Of Boolean)("ToolHelpChinese"), "zh_cn", "en_us"), CurrentLang.Lower)
If Not McInstanceSelected.Version.Vaild OrElse McInstanceSelected.Version.Vanilla.Major <= 10 Then
If Not McInstanceSelected.Version.Valid OrElse McInstanceSelected.Version.Vanilla.Major <= 10 Then
'将最后两位改为大写,前面的部分保留
RequiredLang = RequiredLang.Substring(0, RequiredLang.Length - 2) & RequiredLang.Substring(RequiredLang.Length - 2).Upper
End If
@@ -1867,53 +1894,40 @@ NextInstance:
If McLoginLoader.Input.Type = McLoginType.Legacy AndAlso Settings.Get(Of Integer)("LaunchSkinType") = 4 AndAlso FileUtils.Exists(Paths.AppDataThenName & "CustomSkin.png") Then
Dim MetaFileAddress As String = PathTemp & "pack.mcmeta"
Dim PackPicAddress As String = PathTemp & "pack.png"
Dim PackFormat As Integer
Select Case McInstanceSelected.Version.Vanilla.Major
Case 0, 1, 2, 3, 4, 5
'更早的版本没有资源包
If McInstanceSelected.Version.Vanilla.Major < 6 Then
McLaunchLog("Minecraft 版本过老,尚不支持自定义离线皮肤")
GoTo IgnoreCustomSkin
End If
Dim PackVersionJToken As JToken = McInstanceSelected.JsonVersion?("pack_version")
If TypeOf PackVersionJToken Is JObject Then PackVersionJToken = DirectCast(PackVersionJToken, JObject)("resource") '从 1.17 开始,可以从 pack_version.resource 获取资源包版本
Dim PackVersion As Integer = Val(PackVersionJToken?.ToString)
If PackVersion <= 0 Then '没有对应字段的老版本 MC,或兜底
Select Case McInstanceSelected.Version.Vanilla.Major
Case 6, 7, 8
PackFormat = 1
PackVersion = 1
Case 9, 10
PackFormat = 2
PackVersion = 2
Case 11, 12
PackFormat = 3
PackVersion = 3
Case 13, 14
PackFormat = 4
PackVersion = 4
Case 15
PackFormat = 5
PackVersion = 5
Case 16
PackFormat = 6
PackVersion = If(McInstanceSelected.Version.Vanilla.Build <= 1, 5, 6)
Case 17
PackFormat = 7
PackVersion = 7
Case 18
If McInstanceSelected.Version.Vanilla.Minor <= 2 Then
PackFormat = 8
Else
PackFormat = 9
End If
Case 19
If McInstanceSelected.Version.Vanilla.Minor <= 3 Then
PackFormat = 9
Else
PackFormat = 12
End If
Case 20
If McInstanceSelected.Version.Vanilla.Minor <= 1 Then
PackFormat = 15
Else
PackFormat = 17
End If
Case Else '快照版是 9999
PackFormat = 17
'https://zh.minecraft.wiki/w/数据包#数据包版本
PackVersion = 8
Case Else '旧版快照可能没有可用的资源包格式声明
PackVersion = 17
End Select
McLaunchLog("正在构建自定义皮肤资源包,格式为:" & PackFormat)
End If
McLaunchLog($"正在构建自定义皮肤资源包,版本号 {PackVersion},MC 版本 {McInstanceSelected.Version.Vanilla}")
'准备文件
Dim Bit As New MyBitmap(PathImage & "Heads/Logo.png")
Bit.Save(PackPicAddress)
FileUtils.Write(MetaFileAddress, "{""pack"":{""pack_format"":" & PackFormat & ",""description"":""PCL 自定义离线皮肤资源包""}}")
FileUtils.Write(MetaFileAddress, "{""pack"":{""pack_format"":" & PackVersion & ",""description"":""PCL 自定义离线皮肤资源包""}}")
Dim Skin As New MyBitmap(Paths.AppDataThenName & "CustomSkin.png")
If (McInstanceSelected.Version.Vanilla.Major = 6 OrElse McInstanceSelected.Version.Vanilla.Major = 7) AndAlso Skin.Pic.Height = 64 Then
McLaunchLog("该 Minecraft 版本不支持双层皮肤,已进行裁剪")
@@ -1997,7 +2011,7 @@ IgnoreCustomSkin:
'输出 bat
Try
Dim CmdString As String =
$"{If(McLaunchJavaSelected.Version.Major > 8, "chcp 65001>nul" & vbCrLf, "")}" &
$"chcp {If(McLaunchEncoding.CodePage = 54936 AndAlso IsGBKEncoding, 936, McLaunchEncoding.CodePage)}>nul" & vbCrLf &
"@echo off" & vbCrLf &
$"title 启动 - {McInstanceSelected.Name}" & vbCrLf &
"echo 游戏正在启动,请稍候。" & vbCrLf &
@@ -2010,7 +2024,7 @@ IgnoreCustomSkin:
FileUtils.Write(
filePath:=If(CurrentLaunchOptions.SaveBatch, Paths.Base & "PCL\LatestLaunch.bat"),
text:=FilterAccessToken(CmdString, "F").Replace("%", "%%"),
encoding:=If(McLaunchJavaSelected.Version.Major > 8, Encoding.UTF8, Encoding.Default))
encoding:=McLaunchEncoding)
If CurrentLaunchOptions.SaveBatch IsNot Nothing Then
McLaunchLog("导出启动脚本完成,强制结束启动过程")
CanceledHint = "导出启动脚本成功!"
@@ -2092,6 +2106,8 @@ IgnoreCustomSkin:
'设置其他参数
StartInfo.WorkingDirectory = McInstanceSelected.PathIndie
StartInfo.UseShellExecute = False
StartInfo.StandardOutputEncoding = McLaunchEncoding
StartInfo.StandardErrorEncoding = McLaunchEncoding
StartInfo.RedirectStandardOutput = True
StartInfo.RedirectStandardError = True
StartInfo.CreateNoWindow = True
File diff suppressed because it is too large. Load diff
@@ -8,6 +8,10 @@ Public Module ModModpack
''' 弹窗要求选择一个整合包文件并进行安装。
''' </summary>
Public Sub ModpackInstall()
If RestrictMcDownload Then
Hint("为遵守相关法律法规,PCL 暂时不再提供 MC 下载功能,非常抱歉 qwq……", HintType.Red)
Return
End If
Dim File As String = Dialogs.SelectFile("选择整合包压缩文件", False, filter:={({"rar", "zip", "mrpack"}, "整合包文件")}).FirstOrDefault() '选择整合包文件
If String.IsNullOrEmpty(File) Then Return
RunInThread(
@@ -74,13 +78,14 @@ Public Module ModModpack
Catch ex As Exception
If ex.GetDisplay(True).Contains("Error.WinIOError") Then
Throw New Exception("打开整合包文件失败", ex)
ElseIf File.EndsWithF(".rar", True) Then
ElseIf PathUtils.GetExtension(File) = "rar" Then
Throw New Exception("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试", ex)
Else
Throw New Exception("打开整合包文件失败,文件可能损坏或为不支持的压缩包格式", ex)
End If
End Try
'执行对应的安装方法
If RestrictMcDownload Then Throw New Exception("为遵守相关法律法规,PCL 暂时不再提供 MC 下载功能,非常抱歉 qwq……")
Select Case PackType
Case 0
Logger.Info("整合包种类:CurseForge")
@@ -109,37 +114,18 @@ Public Module ModModpack
End Try
End Function
Private Sub ExtractModpackFiles(InstallTemp As String, FileAddress As String, Loader As LoaderBase, ProgressIncrement As Double)
'解压文件
Dim RetryCount As Integer = 1
Dim InitialProgress = Loader.Progress
Retry:
Try
Loader.Progress = InitialProgress
Private Sub ExtractModpackFiles(InstallTemp As String, FileAddress As String, Optional c As CancellationToken = Nothing, Optional p As ProgressProvider = Nothing)
DirectoryUtils.Delete(InstallTemp)
Dim RawProgress As Double = Loader.Progress
FileUtils.ExtractToDirectory(FileAddress, InstallTemp, progressHandler:=Sub(Percentage) Loader.Progress = RawProgress + Percentage * ProgressIncrement)
Catch ex As Exception
Logger.Warn(ex, $"第 {RetryCount} 次解压尝试失败")
'完全不知道为啥会出现文件正在被另一进程使用的问题,总之加个重试
If RetryCount < 5 Then
Thread.Sleep(RetryCount * 2000)
If Loader IsNot Nothing AndAlso Loader.LoadingState <> MyLoading.MyLoadingState.Run Then Return
RetryCount += 1
GoTo Retry
Else
Throw New Exception("解压整合包文件失败", ex)
End If
End Try
Loader.Progress = InitialProgress + ProgressIncrement
c.ThrowIfCancellationRequested()
FileUtils.ExtractToDirectory(FileAddress, InstallTemp, c, p)
End Sub
''' <summary>
''' 从整合包的 override 目录复制文件,同时设置 PCL 的配置文件与版本隔离。
''' 对路径末尾是否为 \ 没有要求。
''' </summary>
Private Sub CopyOverrideDirectory(OverridesFolder As String, VersionFolder As String, Loader As LoaderBase)
If Not OverridesFolder.EndsWithF("\") Then OverridesFolder += "\"
If Not VersionFolder.EndsWithF("\") Then VersionFolder += "\"
OverridesFolder = PathUtils.AddSlashSuffix(OverridesFolder)
VersionFolder = PathUtils.AddSlashSuffix(VersionFolder)
'复制文件
If DirectoryUtils.Exists(OverridesFolder) Then
Logger.Info($"处理整合包覆写文件夹:{OverridesFolder} → {VersionFolder}")
@@ -193,27 +179,26 @@ Retry:
If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate})
If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException
End If
Dim VersionFolder As String = PathUtils.AddSlashSuffix(Path.Combine(McFolderSelected, "versions", InstanceName) & "\")
'获取 Mod API 版本信息
Dim TargetVersion As New McVersion
TargetVersion.VanillaName = Json("minecraft")("version").ToString
Dim TargetVersion As New McVersion(Json("minecraft")("version").ToString)
For Each Entry In If(Json("minecraft")("modLoaders"), {})
Dim Id As String = If(Entry("id"), "").ToString.Lower
If Id.StartsWithF("forge-") Then
'Forge 指定
If Id.Contains("recommended") Then Throw New Exception("该整合包版本过老,已不支持进行安装!")
Logger.Info($"整合包 Forge 版本:{Id}")
TargetVersion.Forge = Id.Replace("forge-", "")
TargetVersion.ModLoaderVersions(ModLoaders.Forge) = Id.Replace("forge-", "")
ElseIf Id.StartsWithF("neoforge-") Then
'NeoForge 指定
Logger.Info($"整合包 NeoForge 版本:{Id}")
TargetVersion.NeoForge = Id.Replace("neoforge-", "")
TargetVersion.ModLoaderVersions(ModLoaders.NeoForge) = Id.Replace("neoforge-", "")
ElseIf Id.StartsWithF("fabric-") Then
'Fabric 指定
Logger.Info($"整合包 Fabric 版本:{Id}")
TargetVersion.Fabric = Id.Replace("fabric-", "")
TargetVersion.ModLoaderVersions(ModLoaders.Fabric) = Id.Replace("fabric-", "")
Else
'ElseIf Id.StartsWithF("quilt-") Then
NotifyIncompatibleLoader(Id)
End If
Next
@@ -222,12 +207,12 @@ Retry:
Dim InstallLoaders As New List(Of LoaderBase)
Dim OverrideHome As String = If(Json("overrides"), "")
If OverrideHome <> "" Then
Dim OverridesFolder As String = InstallTemp & ArchiveBaseFolder & If(OverrideHome = "." OrElse OverrideHome = "./", "", OverrideHome) '#5613
If Not PathUtils.IsParentOf(InstallTemp, OverridesFolder) Then Throw New Exception("整合包的 overrides 路径超出了解压目录,请向整合包作者反馈此问题!")
InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
Sub(Task As LoaderTask(Of String, Integer))
ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
CopyOverrideDirectory(
InstallTemp & ArchiveBaseFolder & If(OverrideHome = "." OrElse OverrideHome = "./", "", OverrideHome), '#5613
$"{McFolderSelected}versions\{InstanceName}", Task)
ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.6))
CopyOverrideDirectory(OverridesFolder, VersionFolder, Task)
End Sub) With {
.ProgressWeight = FileUtils.GetInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
End If
@@ -296,13 +281,15 @@ Retry:
Dim TargetFolder As String, Type As ResourceTypes
If ModJson("modules").Any Then 'modules 可能返回 null(#1006)
Dim ModuleNames = CType(ModJson("modules"), JArray).Select(Function(l) l("name").ToString).ToList
If ModuleNames.Contains("META-INF") OrElse ModuleNames.Contains("mcmod.info") OrElse
If ModuleNames.Contains("META-INF") OrElse ModuleNames.Contains("mcmod.info") OrElse ModuleNames.Contains("fabric.mod.json") OrElse
ModJson?("FileName")?.ToString.EndsWithF(".jar", True) Then
TargetFolder = "mods" : Type = ResourceTypes.Mod
ElseIf ModuleNames.Contains("pack.mcmeta") Then
TargetFolder = "resourcepacks" : Type = ResourceTypes.ResourcePack
Else
ElseIf ModuleNames.Contains("shaders") Then
TargetFolder = "shaderpacks" : Type = ResourceTypes.Shader
Else
TargetFolder = "saves" : Type = ResourceTypes.Map
End If
Else
TargetFolder = "mods" : Type = ResourceTypes.Mod
@@ -311,7 +298,7 @@ Retry:
Dim File = ResourceVersion.FromPlatformJson(ModJson, Type)
If Not File.DownloadAvailable Then Continue For
'实际的添加
FileList.Add(Id, File.ToNetFile($"{McFolderSelected}versions\{InstanceName}\{TargetFolder}\",
FileList.Add(Id, File.ToNetFile(PathUtils.AddSlashSuffix(Path.Combine(VersionFolder, TargetFolder)),
ResourceVersion.DownloadReason.ModPack, TargetVersion.VanillaName, TargetVersion.ModLoaders))
Task.Progress += 1 / (1 + ModList.Count)
Next
@@ -325,15 +312,9 @@ Retry:
End If
'构造加载器
Dim Request As New McInstallRequest With {
.NewInstanceName = InstanceName,
.VersionFolder = $"{McFolderSelected}versions\{InstanceName}\",
.MinecraftName = TargetVersion.VanillaName,
.ForgeVersion = TargetVersion.Forge,
.NeoForgeVersion = TargetVersion.NeoForge,
.FabricVersion = TargetVersion.Fabric
}
Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request)
Dim Target As New McComponents(TargetVersion.VanillaName, TargetVersion.ModLoaderVersions)
Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With {
.NewInstanceName = InstanceName, .InstanceFolder = VersionFolder, .Components = Target})
'构造总加载器
Dim Loaders As New List(Of LoaderBase)
Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
@@ -341,18 +322,23 @@ Retry:
Loaders.Add(New LoaderTask(Of String, String)("最终整理文件",
Sub(Task As LoaderTask(Of String, String))
'设置图标
Dim VersionFolder As String = $"{McFolderSelected}versions\{InstanceName}\"
If Logo IsNot Nothing AndAlso FileUtils.Exists(Logo) Then
FileUtils.Copy(Logo, VersionFolder & "PCL\Logo.png")
WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png")
WriteIni(VersionFolder & "PCL\Setup.ini", "LogoCustom", "True")
Logger.Info($"已设置整合包 Logo:{Logo}")
End If
'解压地图文件
Dim SaveFolder As String = $"{VersionFolder}saves\"
For Each MapFile In DirectoryUtils.EnumerateFiles(SaveFolder, searchPattern:="*.zip")
FileUtils.ExtractToDirectory(MapFile, SaveFolder, Task.CreateCancellationToken())
FileUtils.Delete(MapFile)
Next
'删除原始整合包文件
For Each Target As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
If FileUtils.Exists(Target) Then
Logger.Info($"删除原始整合包文件:{Target}")
FileUtils.Delete(Target)
For Each FileToDelete As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
If FileUtils.Exists(FileToDelete) Then
Logger.Info($"删除原始整合包文件:{FileToDelete}")
FileUtils.Delete(FileToDelete)
End If
Next
If FileUtils.Exists(FileAddress) AndAlso PathUtils.GetFileNameWithoutExtension(FileAddress) = "modpack" Then
@@ -370,10 +356,9 @@ Retry:
'启动
Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
Loader.Start(Request.VersionFolder)
Loader.Start(VersionFolder)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
Return Loader
End Function
@@ -389,22 +374,22 @@ Retry:
End Try
If Json("dependencies") Is Nothing OrElse Json("dependencies")("minecraft") Is Nothing Then Throw New Exception("Modrinth 整合包未提供 Minecraft 版本信息")
'获取 Mod API 版本信息
Dim TargetVersion As New McVersion
'TODO: 改为使用类似 InstallPackMMC 中,一个版本名一个字典的方式来存储它,不要用 Unknown
Dim TargetVersion As New McVersion("Unknown")
For Each Entry As JProperty In If(Json("dependencies"), {})
Select Case Entry.Name.Lower
Case "minecraft"
TargetVersion.VanillaName = Entry.Value.ToString
Case "forge" 'eg. 14.23.5.2859 / 1.19-41.1.0
TargetVersion.Forge = Entry.Value.ToString
Logger.Info($"整合包 Forge 版本:{TargetVersion.Forge}")
TargetVersion.ModLoaderVersions(ModLoaders.Forge) = Entry.Value.ToString
Logger.Info($"整合包 Forge 版本:{TargetVersion.ModLoaderVersions(ModLoaders.Forge)}")
Case "neoforge", "neo-forge" 'eg. 20.6.98-beta
TargetVersion.NeoForge = Entry.Value.ToString
Logger.Info($"整合包 NeoForge 版本:{TargetVersion.NeoForge}")
TargetVersion.ModLoaderVersions(ModLoaders.NeoForge) = Entry.Value.ToString
Logger.Info($"整合包 NeoForge 版本:{TargetVersion.ModLoaderVersions(ModLoaders.NeoForge)}")
Case "fabric-loader" 'eg. 0.14.14
TargetVersion.Fabric = Entry.Value.ToString
Logger.Info($"整合包 Fabric 版本:{TargetVersion.Fabric}")
TargetVersion.ModLoaderVersions(ModLoaders.Fabric) = Entry.Value.ToString
Logger.Info($"整合包 Fabric 版本:{TargetVersion.ModLoaderVersions(ModLoaders.Fabric)}")
Case Else
'Case "quilt-loader" 'eg. 1.0.0
NotifyIncompatibleLoader(Entry.Name)
End Select
Next
@@ -416,18 +401,15 @@ Retry:
If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate})
If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException
End If
Dim VersionFolder As String = Path.Combine(McFolderSelected, "versions", InstanceName) & "\"
'解压
Dim InstallTemp As String = RequestTaskTempFolder()
Dim InstallLoaders As New List(Of LoaderBase)
InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
Sub(Task As LoaderTask(Of String, Integer))
ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.5)
CopyOverrideDirectory(
InstallTemp & ArchiveBaseFolder & "overrides",
McFolderSelected & "versions\" & InstanceName, Task)
CopyOverrideDirectory(
InstallTemp & ArchiveBaseFolder & "client-overrides",
McFolderSelected & "versions\" & InstanceName, Task)
ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.5))
CopyOverrideDirectory(InstallTemp & ArchiveBaseFolder & "overrides", VersionFolder, Task)
CopyOverrideDirectory(InstallTemp & ArchiveBaseFolder & "client-overrides", VersionFolder, Task)
End Sub) With {.ProgressWeight = FileUtils.GetInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
'获取下载文件列表
Dim FileList As New List(Of NetFile)
@@ -450,28 +432,23 @@ Retry:
Urls = ResourceVersion.ParseModrinthTrackArguments(Urls,
ResourceVersion.DownloadReason.ModPack, TargetVersion.VanillaName, TargetVersion.ModLoaders).ToList 'Modrinth 来源追踪信息
Urls = Urls.Distinct.ToList()
Dim TargetPath As String = $"{McFolderSelected}versions\{InstanceName}\{File("path")}"
If Not Path.GetFullPath(TargetPath).StartsWithF(Path.GetFullPath($"{McFolderSelected}versions\{InstanceName}\")) Then
Dim TargetPath As String = Path.Combine(VersionFolder, File("path").ToString().TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
If Not PathUtils.IsParentOf(VersionFolder, TargetPath) Then
MyMsgBox($"整合包的文件路径超出了版本文件夹,请向整合包作者反馈此问题!{vbCrLf}目标:{Path.GetFullPath(TargetPath)}{vbCrLf}错误的文件:{TargetPath}", "文件路径校验失败", IsWarn:=True)
Throw New OperationCanceledException
End If
FileList.Add(New NetFile(Urls, TargetPath,
New FileChecker With {.ActualSize = File("fileSize").ToObject(Of Long), .Hash = File("hashes")("sha1").ToString}, True))
New FileChecker With {.Hash = File("hashes")("sha1").ToString}, True)) 'Modrinth 整合包不应校验文件大小(他们官方自己说的)
Next
If FileList.Any Then
InstallLoaders.Add(New LoaderDownload("下载额外文件", FileList) With {.ProgressWeight = FileList.Count * 1.5}) '每个 Mod 需要 1.5s
End If
'构造加载器
Dim Request As New McInstallRequest With {
.NewInstanceName = InstanceName,
.VersionFolder = $"{McFolderSelected}versions\{InstanceName}\",
.MinecraftName = TargetVersion.VanillaName,
.ForgeVersion = TargetVersion.Forge,
.NeoForgeVersion = TargetVersion.NeoForge,
.FabricVersion = TargetVersion.Fabric
}
Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request)
Dim Target As New McComponents(TargetVersion.VanillaName, TargetVersion.ModLoaderVersions)
Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With {
.NewInstanceName = InstanceName, .InstanceFolder = VersionFolder,
.Components = Target})
'构造总加载器
Dim Loaders As New List(Of LoaderBase)
Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
@@ -479,7 +456,6 @@ Retry:
Loaders.Add(New LoaderTask(Of String, String)("最终整理文件",
Sub(Task As LoaderTask(Of String, String))
'设置图标
Dim VersionFolder As String = $"{McFolderSelected}versions\{InstanceName}\"
If Logo IsNot Nothing AndAlso FileUtils.Exists(Logo) Then
FileUtils.Copy(Logo, VersionFolder & "PCL\Logo.png")
WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png")
@@ -487,10 +463,10 @@ Retry:
Logger.Info($"已设置整合包 Logo:{Logo}")
End If
'删除原始整合包文件
For Each Target As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
If FileUtils.Exists(Target) Then
Logger.Info($"删除原始整合包文件:{Target}")
FileUtils.Delete(Target)
For Each FileToDelete As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
If FileUtils.Exists(FileToDelete) Then
Logger.Info($"删除原始整合包文件:{FileToDelete}")
FileUtils.Delete(FileToDelete)
End If
Next
If FileUtils.Exists(FileAddress) AndAlso PathUtils.GetFileNameWithoutExtension(FileAddress) = "modpack" Then
@@ -508,10 +484,9 @@ Retry:
'启动
Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
Loader.Start(Request.VersionFolder)
Loader.Start(VersionFolder)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
Return Loader
End Function
@@ -530,24 +505,21 @@ Retry:
If Validate.Validate(InstanceName) <> "" Then InstanceName = ""
If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate})
If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException
Dim VersionFolder As String = Path.Combine(McFolderSelected, "versions", InstanceName) & "\"
'解压
Dim InstallTemp As String = RequestTaskTempFolder()
Dim InstallLoaders As New List(Of LoaderBase)
InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
Sub(Task As LoaderTask(Of String, Integer))
ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
CopyOverrideDirectory(
InstallTemp & ArchiveBaseFolder & "minecraft",
McFolderSelected & "versions\" & InstanceName, Task)
ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.6))
CopyOverrideDirectory(InstallTemp & ArchiveBaseFolder & "minecraft", VersionFolder, Task)
End Sub) With {.ProgressWeight = FileUtils.GetInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
'构造游戏本体安装加载器
If Json("gameVersion") Is Nothing Then Throw New Exception("该 HMCL 整合包未提供游戏版本信息,无法安装!")
Dim Request As New McInstallRequest With {
.NewInstanceName = InstanceName,
.VersionFolder = $"{McFolderSelected}versions\{InstanceName}\",
.MinecraftName = Json("gameVersion").ToString
}
Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request)
Dim Target As New McComponents(Json("gameVersion").ToString, New Dictionary(Of ModLoaders, String))
Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With {
.NewInstanceName = InstanceName, .InstanceFolder = VersionFolder,
.Components = Target})
'构造总加载器
Dim Loaders As New List(Of LoaderBase) From {
New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)},
@@ -561,10 +533,9 @@ Retry:
End If
'启动
Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
Loader.Start(Request.VersionFolder)
Loader.Start(VersionFolder)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
Return Loader
End Function
@@ -584,16 +555,22 @@ Retry:
If Validate.Validate(InstanceName) <> "" Then InstanceName = ""
If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate})
If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException
Dim VersionFolder As String = Path.Combine(McFolderSelected, "versions", InstanceName) & "\"
'解压
Dim InstallTemp As String = RequestTaskTempFolder()
Dim SetupFile As String = $"{McFolderSelected}versions\{InstanceName}\PCL\Setup.ini"
Dim SetupFile As String = VersionFolder & "PCL\Setup.ini"
Dim InstallLoaders As New List(Of LoaderBase)
InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
Sub(Task As LoaderTask(Of String, Integer))
ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.55)
CopyOverrideDirectory(
InstallTemp & ArchiveBaseFolder & ".minecraft",
McFolderSelected & "versions\" & InstanceName, Task)
ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.55))
'Prism 优先使用 minecraft/ 而非 .minecraft/(#9272)
Dim MmcGameRoot As String
If DirectoryUtils.Exists(InstallTemp & ArchiveBaseFolder & ".minecraft") AndAlso Not DirectoryUtils.Exists(InstallTemp & ArchiveBaseFolder & "minecraft") Then
MmcGameRoot = InstallTemp & ArchiveBaseFolder & ".minecraft"
Else
MmcGameRoot = InstallTemp & ArchiveBaseFolder & "minecraft"
End If
CopyOverrideDirectory(MmcGameRoot, VersionFolder, Task)
'读取 MMC 设置文件(#2655)
Try
Dim MmcSetupFile As String = InstallTemp & ArchiveBaseFolder & "instance.cfg"
@@ -627,7 +604,7 @@ Retry:
If Logo <> "" AndAlso FileUtils.Exists($"{InstallTemp}{ArchiveBaseFolder}{Logo}.png") Then
WriteIni(SetupFile, "LogoCustom", True)
WriteIni(SetupFile, "Logo", "PCL\Logo.png")
FileUtils.Copy($"{InstallTemp}{ArchiveBaseFolder}{Logo}.png", $"{McFolderSelected}versions\{InstanceName}\PCL\Logo.png")
FileUtils.Copy($"{InstallTemp}{ArchiveBaseFolder}{Logo}.png", VersionFolder & "PCL\Logo.png")
Logger.Info($"迁移 MultiMC 版本独立设置:版本图标({Logo}.png)")
End If
'JVM 参数
@@ -649,20 +626,20 @@ Retry:
End Sub) With {.ProgressWeight = FileUtils.GetInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
'构造版本安装请求
If PackJson("components") Is Nothing Then Throw New Exception("该 MMC 整合包未提供游戏版本信息,无法安装!")
Dim Request As New McInstallRequest With {.NewInstanceName = InstanceName, .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\"}
Dim MinecraftVersion As String = Nothing
Dim ModLoaderVersions As New Dictionary(Of ModLoaders, String)
For Each Component As JObject In PackJson("components")
If Not Component.ContainsKey("uid") Then Continue For
Dim UID As String = Component("uid").ToString
Select Case UID
Case "net.minecraft"
Request.MinecraftName = Component("version")
MinecraftVersion = Component("version")
Case "net.minecraftforge"
Request.ForgeVersion = Component("version")
ModLoaderVersions(ModLoaders.Forge) = Component("version")
Case "net.neoforged"
Request.NeoForgeVersion = Component("version")
ModLoaderVersions(ModLoaders.NeoForge) = Component("version")
Case "net.fabricmc.fabric-loader"
Request.FabricVersion = Component("version")
'Case "org.quiltmc.quilt-loader" 'eg. 1.0.0
ModLoaderVersions(ModLoaders.Fabric) = Component("version")
Case Else
If UID.StartsWithF("org.lwjgl") Then '#8210
Logger.Info($"已跳过 LWJGL 项:{UID}")
@@ -672,7 +649,10 @@ Retry:
End Select
Next
'构造加载器
Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request)
Dim Target As New McComponents(MinecraftVersion, ModLoaderVersions)
Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With {
.NewInstanceName = InstanceName, .InstanceFolder = VersionFolder,
.Components = Target})
'构造总加载器
Dim Loaders As New List(Of LoaderBase)
Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
@@ -687,10 +667,9 @@ Retry:
'启动
Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
Loader.Start(Request.VersionFolder)
Loader.Start(VersionFolder)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
Return Loader
End Function
@@ -713,16 +692,15 @@ Retry:
If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate})
If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException
End If
Dim VersionFolder As String = Path.Combine(McFolderSelected, "versions", InstanceName) & "\"
'解压
Dim InstallTemp As String = RequestTaskTempFolder()
Dim SetupFile As String = $"{McFolderSelected}versions\{InstanceName}\PCL\Setup.ini"
Dim SetupFile As String = VersionFolder & "PCL\Setup.ini"
Dim InstallLoaders As New List(Of LoaderBase)
InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
Sub(Task As LoaderTask(Of String, Integer))
ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
CopyOverrideDirectory(
InstallTemp & ArchiveBaseFolder & "overrides",
McFolderSelected & "versions\" & InstanceName, Task)
ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.6))
CopyOverrideDirectory(InstallTemp & ArchiveBaseFolder & "overrides", VersionFolder, Task)
'JVM 参数
If Json("launchInfo") IsNot Nothing Then
Dim LaunchInfo As JObject = Json("launchInfo")
@@ -738,19 +716,29 @@ Retry:
Next
If Not Addons.ContainsKey("game") Then Throw New Exception("该 MCBBS 整合包未提供游戏版本信息,无法安装!")
If Addons.ContainsKey("quilt") Then
Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Red)
Hint("PCL 不支持安装 Quilt 整合包!", HintType.Red)
Throw New OperationCanceledException
End If
Dim Request As New McInstallRequest With {
.NewInstanceName = InstanceName,
.VersionFolder = $"{McFolderSelected}versions\{InstanceName}\",
.MinecraftName = Addons("game"),
.OptiFineVersion = If(Addons.ContainsKey("optifine"), Addons("optifine"), Nothing),
.ForgeVersion = If(Addons.ContainsKey("forge"), Addons("forge"), Nothing),
.NeoForgeVersion = If(Addons.ContainsKey("neoforge"), Addons("neoforge"), Nothing),
.FabricVersion = If(Addons.ContainsKey("fabric"), Addons("fabric"), Nothing)
}
Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request)
Dim ModLoaderVersions As New Dictionary(Of ModLoaders, String)
If Addons.ContainsKey("forge") Then ModLoaderVersions(ModLoaders.Forge) = Addons("forge")
If Addons.ContainsKey("neoforge") Then ModLoaderVersions(ModLoaders.NeoForge) = Addons("neoforge")
If Addons.ContainsKey("fabric") Then ModLoaderVersions(ModLoaders.Fabric) = Addons("fabric")
Dim OptiFineEntry As JObject = Nothing
If Addons.ContainsKey("optifine") Then
Dim MinecraftVersion = Addons("game")
If MinecraftVersion.EndsWithF(".0") Then MinecraftVersion = MinecraftVersion.Substring(0, MinecraftVersion.Length - 2)
Dim OptiFineFileSuffix = "_HD_" & Addons("optifine").AfterLast("HD_") & ".jar"
'从镜像源拉取 OptiFine 列表
If DlOptiFineListBmclapiLoader.State <> LoadState.Finished Then DlOptiFineListBmclapiLoader.WaitForExit()
Dim Entry = DlOptiFineListBmclapiLoader.Output.Value.FirstOrDefault(
Function(v) v.Inherit = MinecraftVersion AndAlso v.FileName.EndsWithF(OptiFineFileSuffix))
If Entry Is Nothing Then Throw New Exception($"OptiFine 列表中没有整合包所需的 OptiFine {Addons("optifine")}(Minecraft {Addons("game")})")
OptiFineEntry = JObject.FromObject(Entry)
End If
Dim Target As New McComponents(Addons("game"), ModLoaderVersions, OptiFineEntry)
Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With {
.NewInstanceName = InstanceName, .InstanceFolder = VersionFolder,
.Components = Target})
'构造总加载器
Dim Loaders As New List(Of LoaderBase)
Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
@@ -766,10 +754,9 @@ Retry:
'启动
Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件”
Loader.Start(Request.VersionFolder)
Loader.Start(VersionFolder)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
Return Loader
End Function
@@ -784,7 +771,7 @@ Retry:
Dim Loader As New LoaderCombo(Of String)("解压压缩包", {
New LoaderTask(Of String, Integer)("解压压缩包",
Sub(Task As LoaderTask(Of String, Integer))
ExtractModpackFiles(TargetFolder, FileAddress, Task, 0.9)
ExtractModpackFiles(TargetFolder, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.9))
OpenExplorer(TargetFolder)
Thread.Sleep(400) '避免文件争用
'查找解压后的 exe 文件
@@ -860,7 +847,7 @@ Retry:
Dim Loader As New LoaderCombo(Of String)("解压压缩包", {
New LoaderTask(Of String, Integer)("解压压缩包",
Sub(Task As LoaderTask(Of String, Integer))
ExtractModpackFiles(TargetFolder, FileAddress, Task, 0.95)
ExtractModpackFiles(TargetFolder, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.95))
'加入文件夹列表
PageSelectLeft.AddFolder(TargetFolder & ArchiveBaseFolder, PathUtils.GetLastPart(TargetFolder), False)
Thread.Sleep(400) '避免文件争用
@@ -8,7 +8,8 @@ Public Module ModWatcher
Dim IsRunning As Boolean = False
Dim TriggerLauncherShutdown As Boolean = True
For Each Watcher In McWatcherList
If Watcher.State = Watcher.MinecraftState.Loading OrElse Watcher.State = Watcher.MinecraftState.Running Then
If Watcher.State = Watcher.MinecraftState.Loading OrElse Watcher.State = Watcher.MinecraftState.Running OrElse
(Watcher.State = Watcher.MinecraftState.Crashed AndAlso Not Watcher.GameProcess.HasExited) Then
IsRunning = True
Exit For
ElseIf Watcher.State = Watcher.MinecraftState.Crashed OrElse Watcher.State = Watcher.MinecraftState.Canceled Then
@@ -91,7 +92,9 @@ Public Module ModWatcher
RunInNewThread(
Sub()
Try
Do Until State = MinecraftState.Ended OrElse State = MinecraftState.Crashed OrElse State = MinecraftState.Canceled OrElse Loader.State = LoadState.Canceled
Do Until State = MinecraftState.Ended OrElse State = MinecraftState.Canceled OrElse
(Loader.State = LoadState.Canceled AndAlso State <> MinecraftState.Crashed) OrElse
(State = MinecraftState.Crashed AndAlso GameProcess.HasExited)
TimerWindow()
TimerLog()
'设置窗口标题
@@ -103,6 +106,7 @@ Public Module ModWatcher
Thread.Sleep(64)
Next
Loop
If State = MinecraftState.Crashed Then WatcherStateChanged()
WatcherLog("Minecraft 日志监控已退出")
Catch ex As Exception
Logger.Error(ex, "Minecraft 日志监控主循环出错")
@@ -314,14 +318,17 @@ Public Module ModWatcher
Dim str As New StringBuilder(512)
GetClassName(hwnd, str, str.Capacity)
Dim ClassName As String = str.ToString
If Not (ClassName = "GLFW30" OrElse ClassName = "LWJGL" OrElse ClassName = "SunAwtFrame") Then Return
If Not (ClassName = "GLFW30" OrElse ClassName = "SDL_app" OrElse ClassName = "LWJGL" OrElse ClassName = "SunAwtFrame") Then Return
'SDL 会先创建隐藏窗口,待初始化完成后才显示(#9073)
If ClassName = "SDL_app" AndAlso Not IsWindowVisible(hwnd) Then Return
'获取窗口标题名
str = New StringBuilder(512)
GetWindowText(hwnd, str, str.Capacity)
Dim WindowText As String = str.ToString
'有的 Mod 可以修改窗口标题,所以不能检测是否为 Minecraft 打头,这并不准确
'部分版本会搞个 GLFW message window 出来所以得反选
If Not (WindowText.StartsWithF("FML") OrElse (WindowText <> "PopupMessageWindow") AndAlso Not WindowText.StartsWithF("GLFW")) Then Return
'部分版本会搞个 GLFW message window,高版本还有个 Hidden Utility Window,都要反选
If String.IsNullOrWhiteSpace(WindowText) OrElse WindowText = "SDL_app" OrElse WindowText = "PopupMessageWindow" OrElse
WindowText.StartsWithF("GLFW") OrElse WindowText.EndsWithF("Hidden Utility Window") Then Return
'获取窗口关联的进程
Dim ProcessId As Integer
GetWindowThreadProcessId(hwnd, ProcessId)
@@ -342,6 +349,7 @@ Public Module ModWatcher
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (hWnd As Integer, str As StringBuilder, maxCount As Integer) As Integer
Private Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" (hWnd As Integer, str As String) As Boolean
Private Declare Function ShowWindow Lib "user32" (hWnd As IntPtr, cmdWindow As UInteger) As Boolean
Private Declare Function IsWindowVisible Lib "user32" (hWnd As IntPtr) As Boolean
Private Declare Function GetWindowThreadProcessId Lib "user32" (hWnd As IntPtr, ByRef lpdwProcessId As Integer) As Integer
'崩溃处理
+47 -24
View File
@@ -1,6 +1,9 @@
#Region "附加属性"
Imports PCL.FormMain
''' <summary>
''' 用于在 XAML 中初始化列表对象。
''' 附加属性无法在 XAML 中为每个对象初始化独立的列表对象,因此需要一个包装类,然后在 XAML 中显式初始化。
@@ -178,6 +181,8 @@ Public Class CustomEvent
Logger.Info($"执行自定义事件:{Type}, {Arg}")
If Arg Is Nothing Then Arg = ""
Dim Args As String() = Arg.Split("|")
Dim Arg0 As String = Arg.BeforeFirst("|")
Dim Arg1 As String = Arg.AfterFirst("|")
Try
Select Case Type
Case EventType.打开网页
@@ -194,16 +199,16 @@ Public Class CustomEvent
Sub()
Try
'确认实际路径
Dim ActualPaths = GetAbsoluteUrls(Args(0), Type)
Dim ActualPaths = GetAbsoluteUrls(Arg0, Type)
Dim Location = ActualPaths(0), WorkingDir = ActualPaths(1)
Logger.Info($"打开类自定义事件实际路径:{Location},工作目录:{WorkingDir}")
'执行
If Type = EventType.打开帮助 Then
PageOtherHelp.EnterHelpPage(Location)
Else
If Not EventSafetyConfirm("即将执行:" & Location & If(Args.Length >= 2, " " & Args(1), "")) Then Return
If Not EventSafetyConfirm("即将执行:" & Location & If(Args.Length >= 2, " " & Arg1, "")) Then Return
Dim Info As New ProcessStartInfo With {
.Arguments = If(Args.Length >= 2, Args(1), ""),
.Arguments = If(Args.Length >= 2, Arg1, ""),
.FileName = Location,
.WorkingDirectory = WorkingDir
}
@@ -215,19 +220,19 @@ Public Class CustomEvent
End Sub)
Case EventType.启动游戏
If Args(0) = "\current" Then
If Arg0 = "\current" Then
If McInstanceSelected Is Nothing Then
Hint("请先选择一个 Minecraft 版本!", HintType.Red)
Return
Else
Args(0) = McInstanceSelected.Name
Arg0 = McInstanceSelected.Name
End If
End If
RunInUi(
Sub()
If McLaunchStart(New McLaunchOptions With
{.ServerIp = If(Args.Length >= 2, Args(1), Nothing), .Instance = New McInstance(Args(0))}) Then
Hint($"正在启动 {Args(0)}……")
{.ServerIp = If(Args.Length >= 2, Args(1), Nothing), .Instance = New McInstance(Arg0)}) Then
Hint($"正在启动 {Arg0}……")
End If
End Sub)
@@ -264,51 +269,69 @@ Public Class CustomEvent
Case EventType.弹出窗口
If Args.Length = 1 Then Throw New Exception($"EventType {Type} 需要至少 2 个以 | 分割的参数,例如 弹窗标题|弹窗内容")
MyMsgBox(Args(1).Replace("\n", vbCrLf), Args(0).Replace("\n", vbCrLf), If(Args.Length > 2, Args(2), "确定"))
MyMsgBox(Arg1.BeforeLast("|").Replace("\n", vbCrLf), Arg0.Replace("\n", vbCrLf), If(Args.Length > 2, Arg1.AfterLast("|"), "确定"))
Case EventType.弹出提示
Hint(Args(0).Replace("\n", vbCrLf), If(Args.Length = 1, HintType.Blue, EnumUtils.FromString(Of HintType)(Args(1))))
Hint(Arg0.Replace("\n", vbCrLf), If(Args.Length = 1, HintType.Blue, EnumUtils.FromString(Of HintType)(Arg1)))
Case EventType.切换页面
RunInUi(Sub() FrmMain.PageChange(
EnumUtils.FromString(Of FormMain.PageType)(Args(0)),
If(Args.Length = 1, FormMain.PageSubType.Default, EnumUtils.FromString(Of FormMain.PageSubType)(Args(1)))))
RunInUi(
Sub()
Dim TargetPage = EnumUtils.FromString(Of PageType)(Arg0)
If TargetPage = PageType.InstanceSetup Then
If McInstanceSelected Is Nothing Then
Hint("请先选择一个 Minecraft 版本!", HintType.Red)
Return
End If
If McInstanceLock.IsLocked(McInstanceSelected) Then
Hint("该版本正在被修改,暂时无法进入版本设置!", HintType.Red)
Return
End If
PageInstanceLeft.Instance = McInstanceSelected
End If
FrmMain.PageChange(TargetPage,
If(Args.Length = 1, PageSubType.Default, EnumUtils.FromString(Of PageSubType)(Arg1)))
End Sub)
Case EventType.导入整合包, EventType.安装整合包
RunInUi(Sub() ModpackInstall())
Case EventType.下载文件
Args(0) = Args(0).Replace("\", "/")
If Not (Args(0).StartsWithF("http://", True) OrElse Args(0).StartsWithF("https://", True)) Then
Arg0 = Arg0.Replace("\", "/")
If Not (Arg0.StartsWithF("http://", True) OrElse Arg0.StartsWithF("https://", True)) Then
MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "PCL 不支持其他乱七八糟的下载协议。", "事件执行失败")
Return
End If
If Not EventSafetyConfirm("即将从该网址下载文件:" & vbCrLf & Args(0)) Then Return
If Not EventSafetyConfirm("即将从该网址下载文件:" & vbCrLf & Arg0) Then Return
RunInUi(
Sub()
Try
Select Case Args.Length
Case 1
PageOtherTest.StartCustomDownload(Args(0), PathUtils.GetLastPart(Args(0)))
PageOtherTest.StartCustomDownload(Arg0, PathUtils.GetLastPart(Arg0))
Case 2
PageOtherTest.StartCustomDownload(Args(0), Args(1))
PageOtherTest.StartCustomDownload(Arg0, Arg1)
Case Else
PageOtherTest.StartCustomDownload(Args(0), Args(1), Args(2))
PageOtherTest.StartCustomDownload(Arg0, Args(1), Args(2))
End Select
Catch
PageOtherTest.StartCustomDownload(Args(0), "未知")
PageOtherTest.StartCustomDownload(Arg0, "未知")
End Try
End Sub)
Case EventType.修改设置, EventType.写入设置
If Args.Length = 1 Then Throw New Exception($"EventType {Type} 需要至少 2 个以 | 分割的参数,例如 UiLauncherTransparent|400")
Settings.SetSafe(Args(0), Args(1), Instance:=McInstanceSelected)
If Args.Length = 2 Then Hint($"已写入设置:{Args(0)} → {Args(1)}", HintType.Green)
Settings.SetSafe(Arg0, Args(1), Instance:=McInstanceSelected)
If Args.Length = 2 Then Hint($"已写入设置:{Arg0} → {Args(1)}", HintType.Green)
Case EventType.修改变量, EventType.写入变量
If Args.Length = 1 Then Throw New Exception($"EventType {Type} 需要至少 2 个以 | 分割的参数,例如 VariableName|SomeValue")
WriteReg("CustomEvent" & Args(0), Args(1))
If Args.Length = 2 Then Hint($"已写入变量:{Args(0)} → {Args(1)}", HintType.Green)
Try
RegistryUtils.Write($"HKEY_CURRENT_USER\Software\{RegFolder}", $"CustomEvent{Arg0}", Args(1))
If Args.Length = 2 Then Hint($"已写入变量:{Arg0} → {Args(1)}", HintType.Green)
Catch ex As Exception
Logger.Warn(ex, $"写入变量失败:{Arg0} → {Args(1)}", LogBehavior.Toast)
End Try
Case EventType.加入房间
RunInUi(
@@ -347,7 +370,7 @@ Public Class CustomEvent
Dim RawFileName As String
Try
RawFileName = PathUtils.GetLastPart(RelativeUrl)
If Not RawFileName.EndsWithF(".json", True) Then Throw New Exception("未指向 .json 后缀的文件")
If Not PathUtils.GetExtension(RawFileName) = "json" Then Throw New Exception("未指向 .json 后缀的文件")
Catch ex As Exception
Throw New Exception("联网帮助页面须指向一个帮助 JSON 文件,并在同路径下包含相应 XAML 文件!" & vbCrLf &
"例如:" & vbCrLf &
+17 -18
View File
@@ -414,6 +414,7 @@ EndHint:
Public FrmInstanceModDisabled As PageInstanceModDisabled
Public FrmInstanceSetup As PageInstanceSetup
Public FrmInstanceExport As PageInstanceExport
Public FrmDownloadModify As PageDownloadModify
'资源信息分页声明
Public FrmDownloadResourceDetail As PageDownloadResourceDetail
@@ -510,7 +511,7 @@ EndHint:
EventData = If(JsonData("EventData"), "")
IsEvent = True
Else
Dim XamlAddress As String = FilePath.Lower.Replace(".json", ".xaml")
Dim XamlAddress As String = Path.ChangeExtension(FilePath, ".xaml")
If FileUtils.Exists(XamlAddress) Then
XamlContent = FileUtils.ReadAsString(XamlAddress)
IsEvent = False
@@ -596,9 +597,9 @@ EndHint:
'读取自带文件
For Each File In DirectoryUtils.EnumerateFiles(PathTemp & "Help", True)
'跳过非 json 文件与以 . 开头的文件夹
If PathUtils.RemoveLastPart(File).Replace(PathTemp & "Help", "").Contains("\.") OrElse PathUtils.GetExtension(File) <> "json" Then Continue For
If PathUtils.RemoveLastPart(File).Substring((PathTemp & "Help").Length).Contains("\.") OrElse PathUtils.GetExtension(File) <> "json" Then Continue For
'检查忽略列表
Dim RealPath As String = File.Replace(PathTemp & "Help\", "")
Dim RealPath As String = File.Substring((PathTemp & "Help\").Length)
For Each Ignore In IgnoreList
If RealPath.RegexCheck(Ignore) Then
Logger.Trace($"已忽略 {RealPath}:{Ignore}")
@@ -799,26 +800,16 @@ NextFile:
''' 如果失败,则抛出异常。
''' </summary>
Public Sub SetGPUPreference(Executeable As String)
Const REG_KEY As String = "Software\Microsoft\DirectX\UserGpuPreferences"
Const REG_KEY As String = "HKEY_CURRENT_USER\Software\Microsoft\DirectX\UserGpuPreferences"
Const REG_VALUE As String = "GpuPreference=2;"
'查看现有设置
Using ReadOnlyKey = My.Computer.Registry.CurrentUser.OpenSubKey(REG_KEY, False)
If ReadOnlyKey IsNot Nothing Then
If REG_VALUE = ReadOnlyKey.GetValue(Executeable)?.ToString() Then
If RegistryUtils.Read(REG_KEY, Executeable).result?.ToString() = REG_VALUE Then
Logger.Info($"无需调整显卡设置:{Executeable}")
Return
End If
Else
'创建父级键
Logger.Info($"需要创建显卡设置的父级键")
My.Computer.Registry.CurrentUser.CreateSubKey(REG_KEY)
End If
End Using
'写入新设置
Using WriteKey = My.Computer.Registry.CurrentUser.OpenSubKey(REG_KEY, True)
WriteKey.SetValue(Executeable, REG_VALUE)
RegistryUtils.Write(REG_KEY, Executeable, REG_VALUE)
Logger.Info($"已调整显卡设置:{Executeable}")
End Using
End Sub
''' <summary>
@@ -867,11 +858,19 @@ NextFile:
Else
Text = Text.Replace("{version}", Replacer(McInstanceSelected.Version.VanillaName))
End If
Text = Text.Replace("{forge}", Replacer(If(McInstanceSelected.Version.ModLoaders.HasFlagF(ModLoaders.Forge), McInstanceSelected.Version.ModLoaderVersions(ModLoaders.Forge), "")))
Text = Text.Replace("{fabric}", Replacer(If(McInstanceSelected.Version.ModLoaders.HasFlagF(ModLoaders.Fabric), McInstanceSelected.Version.ModLoaderVersions(ModLoaders.Fabric), "")))
Text = Text.Replace("{neoforge}", Replacer(If(McInstanceSelected.Version.ModLoaders.HasFlagF(ModLoaders.NeoForge), McInstanceSelected.Version.ModLoaderVersions(ModLoaders.NeoForge), "")))
Text = Text.Replace("{liteloader}", Replacer(If(McInstanceSelected.Version.ModLoaders.HasFlagF(ModLoaders.LiteLoader), "true", "")))
Else
Text = Text.Replace("{version_path}", Replacer(Nothing)) : Text = Text.Replace("{verpath}", Replacer(Nothing))
Text = Text.Replace("{version_indie}", Replacer(Nothing)) : Text = Text.Replace("{verindie}", Replacer(Nothing))
Text = Text.Replace("{name}", Replacer(Nothing))
Text = Text.Replace("{version}", Replacer(Nothing))
Text = Text.Replace("{forge}", Replacer(Nothing))
Text = Text.Replace("{fabric}", Replacer(Nothing))
Text = Text.Replace("{neoforge}", Replacer(Nothing))
Text = Text.Replace("{liteloader}", Replacer(Nothing))
End If
'登录信息
If McLoginLoader.State = LoadState.Finished Then
@@ -896,8 +895,8 @@ NextFile:
Text = Text.RegexReplace("\{hint\}", Function() Replacer(PageOtherTest.GetRandomHint()))
Text = Text.RegexReplace("\{cave\}", Function() Replacer(PageOtherTest.GetRandomCave()))
Text = Text.RegexReplace("\{setup:([a-zA-Z0-9]+)\}", Function(m) Replacer(Settings.GetSafe(m.Groups(1).Value, McInstanceSelected)))
Text = Text.RegexReplace("\{varible:([^:\}]+)(?::([^\}]+))?\}", Function(m) Replacer(ReadReg("CustomEvent" & m.Groups(1).Value, m.Groups(2).Value)))
Text = Text.RegexReplace("\{variable:([^:\}]+)(?::([^\}]+))?\}", Function(m) Replacer(ReadReg("CustomEvent" & m.Groups(1).Value, m.Groups(2).Value)))
Text = Text.RegexReplace("\{varible:([^:\}]+)(?::([^\}]+))?\}", Function(m) Replacer(RegistryUtils.TryRead($"HKEY_CURRENT_USER\Software\{RegFolder}", $"CustomEvent{m.Groups(1).Value}", m.Groups(2).Value)))
Text = Text.RegexReplace("\{variable:([^:\}]+)(?::([^\}]+))?\}", Function(m) Replacer(RegistryUtils.TryRead($"HKEY_CURRENT_USER\Software\{RegFolder}", $"CustomEvent{m.Groups(1).Value}", m.Groups(2).Value)))
Return Text
End Function
+1 -1
View File
@@ -320,7 +320,7 @@ Public Module ModMusic
Hint("由于音频设备变更,音乐播放功能在重启 PCL 后才能恢复!", HintType.Red)
Thread.Sleep(1000000000)
End If
If Not (MusicCurrent.EndsWithF(".wav", True) OrElse MusicCurrent.EndsWithF(".mp3", True) OrElse MusicCurrent.EndsWithF(".flac", True)) OrElse
If Not ({"wav", "mp3", "flac"}.Contains(PathUtils.GetExtension(MusicCurrent))) OrElse
ex.Message.Contains("0xC00D36C4") Then '#5096:不支持给定的 URL 的字节流类型。 (异常来自 HRESULT:0xC00D36C4)
Hint("播放音乐失败(" & PathUtils.GetLastPart(MusicCurrent) & "):PCL 可能不支持此音乐格式,请将格式转换为 .wav、.mp3 或 .flac 后再试", HintType.Red)
Else
@@ -15,6 +15,7 @@ Friend Module ModSecret
#Region "网络鉴权"
Public Const RestrictMcDownload As Boolean = True
Friend Function SecretCdnSign(UrlWithMark As String) As String
If Not UrlWithMark.EndsWithF("{CDN}") Then Return UrlWithMark
Return UrlWithMark.Replace("{CDN}", "").Replace(" ", "%20")
@@ -25,7 +25,7 @@ Public Class LocalResourceFile
End Get
End Property
''' <summary>
''' 假若该资源未被禁用时,文件的文件名。
''' 假若该资源未被禁用时,文件的文件名。包含 .jar/.litemod 等后缀。
''' </summary>
Public ReadOnly Property EnabledName As String
Get
@@ -57,7 +57,7 @@ Public Class LocalResourceFile
Public Property Display As String
Get
If _Display Is Nothing Then _Display = ProjectVersion?.Display
If _Display Is Nothing Then _Display = PathUtils.GetFileNameWithoutExtension(File.FullName)
If _Display Is Nothing Then _Display = PathUtils.GetFileNameWithoutExtension(EnabledName)
Return _Display
End Get
Set(value As String)
@@ -238,7 +238,7 @@ Public Class LocalResourceFile
Private _CurseForgeHash As UInteger?
''' <summary>
''' 获取用于 Modrinth 信息获取的 Hash 值(SHA1)。
''' 获取用于 Modrinth 信息获取的 Hash 值(SHA1)。固定为小写。
''' </summary>
Public ReadOnly Property ModrinthHash As String
Get
@@ -269,7 +269,7 @@ Public Class LocalResourceFile
End Function
Public Overrides Function Equals(obj As Object) As Boolean
Dim Target = TryCast(obj, LocalResourceFile)
Return Target IsNot Nothing AndAlso File.FullName = Target.File.FullName
Return Target IsNot Nothing AndAlso PathUtils.IsSame(File.FullName, Target.File.FullName)
End Function
#End Region
@@ -24,11 +24,10 @@ Public Module LocalResourceLoaders
'获取 Mod 文件夹下的可用文件列表
Dim ModFileList As New List(Of String)
If DirectoryUtils.Exists(Loader.Input) Then
Dim RawName As String = Loader.Input.Lower
For Each File In DirectoryUtils.EnumerateFiles(Loader.Input, True)
If File.BeforeLast("\").Lower & "\" <> RawName Then
If Not PathUtils.IsSame(PathUtils.RemoveLastPart(File), Loader.Input) Then
'仅当 Forge 1.13- 且文件夹名与版本号相同时,才加载该子文件夹下的 Mod
If Not (PageInstanceLeft.Instance IsNot Nothing AndAlso PageInstanceLeft.Instance.Version.HasForge AndAlso
If Not (PageInstanceLeft.Instance IsNot Nothing AndAlso PageInstanceLeft.Instance.Version.ModLoaders.HasFlagF(ModLoaders.Forge) AndAlso
PageInstanceLeft.Instance.Version.Vanilla.Major < 13 AndAlso
PathUtils.GetLastPart(PathUtils.RemoveLastPart(File)) = $"1.{PageInstanceLeft.Instance.Version.Vanilla.Major}.{PageInstanceLeft.Instance.Version.Vanilla.Build}") Then
Continue For
@@ -36,12 +35,9 @@ Public Module LocalResourceLoaders
End If
Static IsModFile As Func(Of String, Boolean) =
Function(FullName As String)
If FullName Is Nothing OrElse Not FullName.Contains(".") Then Return False
FullName = FullName.Lower
If FullName.EndsWithF(".jar", True) OrElse FullName.EndsWithF(".zip", True) OrElse FullName.EndsWithF(".litemod", True) OrElse
FullName.EndsWithF(".jar.disabled", True) OrElse FullName.EndsWithF(".zip.disabled", True) OrElse FullName.EndsWithF(".litemod.disabled", True) OrElse
FullName.EndsWithF(".jar.old", True) OrElse FullName.EndsWithF(".zip.old", True) OrElse FullName.EndsWithF(".litemod.old", True) Then Return True
Return False
If FullName Is Nothing Then Return False
If {"disabled", "old"}.Contains(PathUtils.GetExtension(FullName)) Then FullName = PathUtils.GetFileNameWithoutExtension(FullName)
Return {"jar", "zip", "litemod"}.Contains(PathUtils.GetExtension(FullName))
End Function
If IsModFile(File) Then ModFileList.Add(File)
Next
@@ -120,12 +116,12 @@ Public Module LocalResourceLoaders
Next
Logger.Info($"有 {Mods.Where(Function(m) m.Project Is Nothing).Count} 个 Mod 需要联网获取信息,{Mods.Where(Function(m) m.Project IsNot Nothing).Count} 个 Mod 需要更新信息")
If Not Mods.Any Then Return
'获取作为检查目标的加载器和版本
'获取作为检查目标的版本
'此处不应向下扩展检查的 MC 小版本,例如 Mod 在更新 1.16.5 后,对早期的 1.16.2 版本发布了修补补丁,这会导致 PCL 将 1.16.5 版本的 Mod 降级到 1.16.2
Dim TargetMcVersion As McVersion = Loader.Input.Target
Dim VanillaVersion = TargetMcVersion.VanillaName
'开始网络获取
Logger.Info($"目标加载器:{ModLoaders},版本:{VanillaVersion}")
Logger.Info($"正在检查 Mod 信息的版本:{VanillaVersion},加载器 {ModLoaders}")
Dim EndedThreadCount As Integer = 0, IsFailed As Boolean = False
Dim CurrentTaskThread As Thread = Thread.CurrentThread
'从 Modrinth 获取信息
@@ -140,6 +136,7 @@ Public Module LocalResourceLoaders
'步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
If ModrinthVersion.Count = 0 Then Return
Dim ModrinthMapping As New Dictionary(Of String, List(Of LocalResourceFile))
Dim ModrinthLoaders As New Dictionary(Of LocalResourceFile, List(Of String))
For Each Entry In Mods
If Not ModrinthVersion.ContainsKey(Entry.ModrinthHash) Then Continue For
If ModrinthVersion(Entry.ModrinthHash)("files")(0)("hashes")("sha1") <> Entry.ModrinthHash Then Continue For
@@ -149,6 +146,8 @@ Public Module LocalResourceLoaders
ModrinthMapping(ProjectId).Add(Entry)
'记录对应的 ProjectVersion
Dim File = ResourceVersion.FromPlatformJson(ModrinthVersion(Entry.ModrinthHash), ResourceTypes.Mod)
ModrinthLoaders(Entry) = ModrinthVersion(Entry.ModrinthHash)("loaders").
Select(Function(l) l.ToString.Lower).Distinct.OrderBy(Function(l) l).ToList
If Entry.ProjectVersion Is Nothing OrElse Entry.ProjectVersion.ReleaseDate < File.ReleaseDate Then
Entry.ProjectVersion = File
Else
@@ -169,17 +168,20 @@ Public Module LocalResourceLoaders
Next
Logger.Info($"已从 Modrinth 获取本地 Mod 信息,继续获取更新信息")
'步骤 4:获取更新信息
If ModLoaders = ModLoaders.None Then
Logger.Warn("该 Minecraft 版本没有可用的 Mod 加载器,不获取 Mod 更新信息")
Return
End If
Dim ModrinthUpdate As JObject = DlModRequest("https://api.modrinth.com/v2/version_files/update", HttpMethod.Post,
$"{{""hashes"": [""{ModrinthMapping.SelectMany(Function(l) l.Value.Select(Function(m) m.ModrinthHash)).Join(""",""")}""], ""algorithm"": ""sha1"",
""loaders"": [""{ModLoaders.Flags.Join(""",""").Lower}""],""game_versions"": [""{VanillaVersion}""]}}", "application/json")
Dim ModsToUpdate = ModrinthMapping.SelectMany(Function(l) l.Value).ToList
If Not ModsToUpdate.Any Then Return
Dim UpdateData As String = ModsToUpdate.
Select(Function(m) $"{{""hash"": ""{m.ModrinthHash}"", ""loaders"": [""{ModrinthLoaders(m).Join(""",""")}""], ""game_versions"": [""{VanillaVersion}""]}}").
Join(",")
Dim ModrinthUpdate As JObject = DlModRequest("https://api.modrinth.com/v2/version_files/update_individual", HttpMethod.Post,
$"{{""algorithm"": ""sha1"", ""hashes"": [{UpdateData}]}}", "application/json")
For Each Entry In Mods
If Not ModrinthUpdate.ContainsKey(Entry.ModrinthHash) OrElse Entry.ProjectVersion Is Nothing Then Continue For
Dim UpdateFile = ResourceVersion.FromPlatformJson(ModrinthUpdate(Entry.ModrinthHash), ResourceTypes.Mod)
If Not UpdateFile.DownloadAvailable Then Continue For
Dim UpdateLoaders = ModrinthUpdate(Entry.ModrinthHash)("loaders").
Select(Function(l) l.ToString.Lower).Distinct.OrderBy(Function(l) l).ToList
If Not UpdateLoaders.SequenceEqual(ModrinthLoaders(Entry)) Then Continue For
Logger.Trace(Function() $"本地文件 {Entry.ProjectVersion.FileName} 在 Modrinth 上的最新版为 {UpdateFile.FileName}")
If Entry.ProjectVersion.ReleaseDate >= UpdateFile.ReleaseDate OrElse Entry.ProjectVersion.Hash = UpdateFile.Hash Then Continue For
'设置更新日志与更新文件
@@ -217,6 +219,7 @@ Public Module LocalResourceLoaders
'步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
Dim ProjectIdToLocalFiles As New Dictionary(Of Integer, List(Of LocalResourceFile))
Dim CurseForgeLoaders As New Dictionary(Of LocalResourceFile, ModLoaders)
For Each Project In CurseForgeRaw
Dim ProjectId = Project("id").ToString
Dim Hash As UInteger = Project("file")("fileFingerprint")
@@ -226,6 +229,7 @@ Public Module LocalResourceLoaders
ProjectIdToLocalFiles.AddIntoValueCollection(ProjectId, Entry)
'记录对应的 ProjectVersion
Dim File = ResourceVersion.FromPlatformJson(Project("file"), ResourceTypes.Mod)
CurseForgeLoaders(Entry) = File.ModLoaders
If Entry.ProjectVersion Is Nothing OrElse Entry.ProjectVersion.ReleaseDate < File.ReleaseDate Then Entry.ProjectVersion = File
Next
Next
@@ -242,36 +246,36 @@ Public Module LocalResourceLoaders
If ProjectJson("isAvailable") IsNot Nothing AndAlso Not ProjectJson("isAvailable").ToObject(Of Boolean) Then Continue For
'设置 Entry 中的工程信息
Dim Project As New ResourceProject(ProjectJson)
Dim TargetLoaders As ModLoaders = ModLoaders.None
For Each LocalFile In ProjectIdToLocalFiles(Project.Id) '倒查防止 CurseForge 返回的内容有漏
If LocalFile.Project?.Platform = ResourcePlatforms.Modrinth Then
LocalFile.Project = LocalFile.Project '再次触发修改事件
Else
LocalFile.Project = Project
TargetLoaders = TargetLoaders Or LocalFile.Project.ModLoaders
End If
Next
TargetLoaders = TargetLoaders And ModLoaders '与目标 MC 版本的加载器取交集
'查找或许版本更新的文件列表
If ModLoaders = ModLoaders.None Then Continue For '无法判断 MC 版本使用的加载器,跳过更新检查
'查找可能有版本更新的文件列表
Dim LatestFiles = ProjectJson("latestFilesIndexes").Where(
Function(Entry)
'Mod Loader 匹配
If Entry("modLoader") Is Nothing Then Return False
Dim EntryLoader As ModLoaders = Resource.FromCurseForgeModLoaderType(Entry("modLoader").ToObject(Of Integer))
If Not TargetLoaders.HasFlag(EntryLoader) Then Return False
Dim EntryLoader As ModLoaders = If(Entry("modLoader") Is Nothing, ModLoaders.None,
Resource.FromCurseForgeModLoaderType(Entry("modLoader").ToObject(Of Integer)))
If CurseForgeLoaders(LocalFile) = ModLoaders.None Then
If EntryLoader <> ModLoaders.None Then Return False
ElseIf Not CurseForgeLoaders(LocalFile).HasFlagF(EntryLoader) Then
Return False
End If
'MC 版本匹配
Dim EntryVersion As String = Entry("gameVersion")
Return EntryVersion IsNot Nothing AndAlso EntryVersion = VanillaVersion
End Function).
MaxByAll(Function(Entry) Entry("gameVersion").ToString, New VersionComparer) '只保留最新的 MC 版本
MaxByAll(Function(Entry) Entry("gameVersion").ToString, Comparer(Of String).Create(Function(x, y) McUtils.CompareVersion(x, y))) '只保留最新的 MC 版本
For Each Entry In LatestFiles
Dim FileId = Entry("fileId").ToObject(Of Integer)
If Not UpdateFileIds.ContainsKey(FileId) Then UpdateFileIds(FileId) = New List(Of LocalResourceFile)
UpdateFileIds(FileId).AddRange(ProjectIdToLocalFiles(Project.Id))
UpdateFileIds(FileId).Add(LocalFile)
FileIdToProjectSlug(FileId) = Project.Slug
Next
Next
Next
Logger.Info($"已从 CurseForge 获取本地 Mod 信息,需要获取 {UpdateFileIds.Count} 个用于检查更新的文件信息")
If Not UpdateFileIds.Any() Then Return
@@ -283,6 +287,10 @@ Public Module LocalResourceLoaders
Dim File = ResourceVersion.FromPlatformJson(FileJson, ResourceTypes.Mod)
If Not File.DownloadAvailable Then Continue For
For Each Entry As LocalResourceFile In UpdateFileIds(File.Id)
If File.ModLoaders <> CurseForgeLoaders(Entry) Then
Logger.Warn($"从 CurseForge 获取到的更新文件 {File.FileName} 加载器为 {File.ModLoaders},与本地文件 {Entry.File.Name} 的加载器 {CurseForgeLoaders(Entry)} 不一致,已忽略", LogBehavior.None)
Continue For
End If
If UpdateFiles.ContainsKey(Entry) AndAlso UpdateFiles(Entry).ReleaseDate >= File.ReleaseDate Then Continue For
UpdateFiles(Entry) = File
Next
@@ -87,7 +87,7 @@ Public Class ResourceProject
Get
If Not LoadedDatabase Then
LoadedDatabase = True
If Types.HasFlag(ResourceTypes.Mod) OrElse Types.HasFlag(ResourceTypes.DataPack) Then
If Types.HasFlagF(ResourceTypes.Mod) OrElse Types.HasFlagF(ResourceTypes.DataPack) Then
_WikiEntry = WikiEntry.All.Value.FirstOrDefault(Function(c) c.Slugs.GetOrDefault(Platform) = Slug)
End If
End If
@@ -184,11 +184,11 @@ Public Class ResourceProject
If Not NewFile.DownloadAvailable Then Continue For
ModLoaders = ModLoaders Or NewFile.ModLoaders
Dim GameVersions = File("gameVersions").ToObject(Of List(Of String))
If Not GameVersions.Any(Function(v) McVersion.IsFormatFit(v)) Then Continue For
If Not GameVersions.Any(Function(v) McVersion.IsStandardFormat(v)) Then Continue For
Files.Add(New KeyValuePair(Of Integer, List(Of String))(File("id"), GameVersions))
Next
For Each File In If(Data("latestFilesIndexes"), New JArray) '这俩玩意儿包含的文件不一样,见 #3599
If Not McVersion.IsFormatFit(File("gameVersion")) Then Continue For
If Not McVersion.IsStandardFormat(File("gameVersion")) Then Continue For
Files.Add(New KeyValuePair(Of Integer, List(Of String))(File("fileId"), {File("gameVersion").ToString}.ToList))
Next
CurseForgeFileIds = Files.Select(Function(f) f.Key).Distinct.ToList
@@ -415,7 +415,7 @@ Public Class ResourceProject
If Not MissingProjectIds.Any() Then Return
'联网获取剩余的工程对象
Dim Projects As JArray
If Platform.HasFlag(ResourcePlatforms.CurseForge) Then
If Platform.HasFlagF(ResourcePlatforms.CurseForge) Then
Projects = DlModRequest("https://api.curseforge.com/v1/mods",
HttpMethod.Post, "{""modIds"": [" & MissingProjectIds.Join(","c) & "]}", "application/json")("data")
Else
@@ -502,7 +502,6 @@ Public Class ResourceProject
'获取 Mod 加载器描述
Dim ModLoaderDescriptionFull As String, ModLoaderDescriptionPart As String
Dim ModLoadersForDesc = ModLoaders.Flags().ToList()
If Settings.Get(Of Boolean)("ToolDownloadIgnoreQuilt") Then ModLoadersForDesc.Remove(ModLoaders.Quilt)
Select Case ModLoadersForDesc.Count
Case 0
If ModLoadersForDesc.IsSingle Then
@@ -519,8 +518,7 @@ Public Class ResourceProject
Dim NewestDrop As Integer = If(Drops.Any, Drops.First, 9999)
If ModLoadersForDesc.Contains(ModLoaders.Forge) AndAlso
(NewestDrop < 140 OrElse ModLoadersForDesc.Contains(ModLoaders.Fabric)) AndAlso
(NewestDrop < 200 OrElse ModLoadersForDesc.Contains(ModLoaders.NeoForge)) AndAlso
(NewestDrop < 140 OrElse ModLoadersForDesc.Contains(ModLoaders.Quilt) OrElse Settings.Get(Of Boolean)("ToolDownloadIgnoreQuilt")) Then
(NewestDrop < 200 OrElse ModLoadersForDesc.Contains(ModLoaders.NeoForge)) Then
ModLoaderDescriptionFull = "任意"
ModLoaderDescriptionPart = ""
Else
@@ -21,8 +21,8 @@ Public Class ResourceSearcher
''' </summary>
Public ReadOnly Property CanContinue As Boolean
Get
If Tag.StartsWithF("/") OrElse Not Sources.HasFlag(ResourcePlatforms.CurseForge) Then Storage.CurseForgeTotal = 0
If Tag.EndsWithF("/") OrElse Not Sources.HasFlag(ResourcePlatforms.Modrinth) Then Storage.ModrinthTotal = 0
If Tag.StartsWithF("/") OrElse Not Sources.HasFlagF(ResourcePlatforms.CurseForge) Then Storage.CurseForgeTotal = 0
If Tag.EndsWithF("/") OrElse Not Sources.HasFlagF(ResourcePlatforms.Modrinth) Then Storage.ModrinthTotal = 0
If Storage.CurseForgeTotal = -1 OrElse Storage.ModrinthTotal = -1 Then Return True
Return Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal
End Get
@@ -99,12 +99,12 @@ Public Class ResourceSearcher
Dim Address As String = $"https://api.modrinth.com/v2/search?limit={RESULT_PAGE_SIZE}&index=relevance"
If Not String.IsNullOrEmpty(SearchText) Then Address += "&query=" & StringUtils.UrlEscape(SearchText)
If Storage.ModrinthOffset > 0 Then Address += "&offset=" & Storage.ModrinthOffset
'facets=[["categories:'game-mechanics'"],["categories:'forge',categories:'fabric'"],["versions:1.19.3"],["project_type:mod"]]
'facets=[["categories:'game-mechanics'"],["categories:forge","categories:fabric"],["versions:1.19.3"],["project_type:mod"]]
Dim Facets As New List(Of String)
Facets.Add($"[""project_type:{Type.ToString.Lower}""]")
If Not String.IsNullOrEmpty(Tag) Then Facets.Add($"[""categories:'{Tag.AfterLast("/")}'""]")
If ModLoaders <> ModLoaders.None AndAlso Not IgnoreModLoaderFilter Then
Facets.Add($"[""categories:'{ModLoaders.Flags.Select(Function(f) f.ToString.Lower).Join("',categories:'")}'""]")
Facets.Add($"[""categories:{ModLoaders.Flags.Select(Function(f) f.ToString.Lower).Join(""",""categories:")}""]")
End If
If Not String.IsNullOrEmpty(GameVersion) Then Facets.Add($"[""versions:'{GameVersion}'""]")
Address += "&facets=[" & String.Join(",", Facets) & "]"
@@ -179,11 +179,6 @@ Public Class ResourceSearcher
End If
End If
'拒绝 1.13- Quilt(这个版本根本没有 Quilt)
If Request.ModLoaders = ModLoaders.Quilt AndAlso CompareVersion(If(Request.GameVersion, "1.15"), "1.14") = -1 Then
Throw New Exception("Quilt 不支持 Minecraft " & Request.GameVersion)
End If
#End Region
#Region "中文搜索"
@@ -246,7 +241,7 @@ Public Class ResourceSearcher
Next
End Function
'CurseForge
If Request.Sources.HasFlag(ResourcePlatforms.CurseForge) Then
If Request.Sources.HasFlagF(ResourcePlatforms.CurseForge) Then
'数据库搜索
Static CurseForgeSearchEntries As List(Of SearchEntry(Of WikiEntry)) = GetSearchEntries(ResourcePlatforms.CurseForge).ToList()
Dim CurseForgeSearchResults = Search(CurseForgeSearchEntries, RawSearchText, 100, 0.25)
@@ -263,7 +258,7 @@ Public Class ResourceSearcher
End If
End If
'Modrinth
If Request.Sources.HasFlag(ResourcePlatforms.Modrinth) Then
If Request.Sources.HasFlagF(ResourcePlatforms.Modrinth) Then
'数据库搜索
Static ModrinthSearchEntries As List(Of SearchEntry(Of WikiEntry)) = GetSearchEntries(ResourcePlatforms.Modrinth).ToList()
Dim ModrinthSearchResults = Search(ModrinthSearchEntries, RawSearchText, 100, 0.25)
@@ -308,7 +303,7 @@ NextPage:
Try
'CurseForge 搜索
If Request.Sources.HasFlag(ResourcePlatforms.CurseForge) AndAlso
If Request.Sources.HasFlagF(ResourcePlatforms.CurseForge) AndAlso
Not (Storage.CurseForgeTotal > -1 AndAlso Storage.CurseForgeTotal <= Storage.CurseForgeOffset) AndAlso '剩余的未显示的搜索结果不足
(Not IsChineseSearch OrElse (IsChineseSearch AndAlso Not String.IsNullOrEmpty(CurseForgeAltSearchText))) Then '如果是中文搜索,就只在有对应搜索关键词的时候才继续
WorkThreads.Add(RunInNewThread(
@@ -322,7 +317,7 @@ NextPage:
For Each JsonEntry As JObject In RequestResult("data")
Dim Project As New ResourceProject(JsonEntry)
If Request.Type = ResourceTypes.ResourcePack AndAlso Project.Tags.Contains("数据包") Then Continue For 'CurseForge 将一些数据包分类成了资源包
If Request.Type <> ResourceTypes.Any AndAlso Not Project.Types.HasFlag(Request.Type) Then Continue For '过滤分区不匹配的搜索结果(#8265)
If Request.Type <> ResourceTypes.Any AndAlso Not Project.Types.HasFlagF(Request.Type) Then Continue For '过滤分区不匹配的搜索结果(#8265)
ProjectList.Add(Project)
Next
'更新结果
@@ -340,7 +335,7 @@ NextPage:
End If
'Modrinth 搜索
If Request.Sources.HasFlag(ResourcePlatforms.Modrinth) AndAlso
If Request.Sources.HasFlagF(ResourcePlatforms.Modrinth) AndAlso
Not (Storage.ModrinthTotal > -1 AndAlso Storage.ModrinthTotal <= Storage.ModrinthOffset) AndAlso '剩余的未显示的搜索结果不足
(Not IsChineseSearch OrElse (IsChineseSearch AndAlso Not String.IsNullOrEmpty(ModrinthAltSearchText))) Then '如果是中文搜索,就只在有对应搜索关键词的时候才继续
WorkThreads.Add(RunInNewThread(
@@ -368,7 +363,7 @@ NextPage:
End If
'Modrinth 直接获取工程
If Request.Sources.HasFlag(ResourcePlatforms.Modrinth) AndAlso
If Request.Sources.HasFlagF(ResourcePlatforms.Modrinth) AndAlso
Not (Storage.ModrinthTotal > -1 AndAlso Storage.ModrinthTotal <= Storage.ModrinthOffset) AndAlso '剩余的未显示的搜索结果不足
ModrinthSlugs.Any Then '有直接获取的 Slug
WorkThreads.Add(RunInNewThread(
@@ -380,7 +375,7 @@ NextPage:
For Each JsonEntry As JObject In DlModRequest(ModrinthUrl)
Dim Project As New ResourceProject(JsonEntry)
'应用筛选
If Request.Type <> ResourceTypes.Any AndAlso Not Project.Types.HasFlag(Request.Type) Then Continue For
If Request.Type <> ResourceTypes.Any AndAlso Not Project.Types.HasFlagF(Request.Type) Then Continue For
If Not String.IsNullOrEmpty(Request.Tag) AndAlso
Not JsonEntry("categories").Any(Function(c) c.ToString = Request.Tag.AfterLast("/")) Then Continue For 'Project.Tags 已经转换成中文了,只能从 json 判
If Request.ModLoaders <> ModLoaders.None AndAlso Not IgnoreModLoaderFilter AndAlso
@@ -389,6 +384,15 @@ NextPage:
Not Project.UnsafeGameVersions.Any(Function(d) d = Request.GameVersion) Then Continue For
ProjectList.Add(Project)
Next
If ProjectList.Any AndAlso Request.ModLoaders <> ModLoaders.None AndAlso Not IgnoreModLoaderFilter AndAlso
Not String.IsNullOrEmpty(Request.GameVersion) Then
Dim ProjectIds = ProjectList.Select(Function(p) p.Id).Join(""",""project_id:")
Dim Loaders = Request.ModLoaders.Flags.Select(Function(f) f.ToString.Lower).Join(""",""categories:")
Dim FilterUrl = $"https://api.modrinth.com/v2/search?limit={ProjectList.Count}&facets=" &
$"[[""project_id:{ProjectIds}""],[""categories:{Loaders}""],[""versions:{Request.GameVersion}""]]"
Dim MatchedIds = DlModRequest(FilterUrl)("hits").Select(Function(h) h("project_id").ToString).ToList
ProjectList.KeepIf(Function(p) MatchedIds.Contains(p.Id))
End If
'更新结果
ProjectList.ForEach(Sub(p) SearchResults.Add(p))
Logger.Info($"从 Modrinth 直接获取到了 {ProjectList.Count} 个工程")
@@ -410,7 +414,7 @@ NextPage:
'仅保留兼容 Forge 的 Mod,或老版本中没有标注任何 Mod Loader 的 Mod
If IgnoreModLoaderFilter Then
RawResults.KeepIf(Function(p) p.ModLoaders = ModLoaders.None OrElse p.ModLoaders.HasFlag(ModLoaders.Forge))
RawResults.KeepIf(Function(p) p.ModLoaders = ModLoaders.None OrElse p.ModLoaders.HasFlagF(ModLoaders.Forge))
End If
'确保存在结果
@@ -90,7 +90,7 @@ Public Class ResourceVersion
''' </summary>
Public DownloadUrls As List(Of String)
''' <summary>
''' 对应文件的 SHA1 或 MD5。
''' 对应文件的 SHA1 或 MD5。固定为小写。
''' </summary>
Public Hash As String = Nothing
''' <summary>
@@ -127,8 +127,9 @@ Public Class ResourceVersion
.DownloadCount = Data("downloadCount")
.FileName = Data("fileName")
.Size = Data("fileLength")
.Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 1)?("value")
If .Hash Is Nothing Then .Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 2)?("value")
If .Size = 0 Then .Size = -1 'CurseForge 可能错误地返回 0 尺寸(#9104)
.Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 1)?("value")?.ToString().Lower()
If .Hash Is Nothing Then .Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 2)?("value")?.ToString().Lower()
'DownloadAddress
Dim Url = Data("downloadUrl").ToString
If Url = "" Then Url = $"https://edge.forgecdn.net/files/{CInt(.Id.ToString.Substring(0, 4))}/{CInt(.Id.ToString.Substring(4))}/{ .FileName}"
@@ -145,13 +146,13 @@ Public Class ResourceVersion
'GameVersions
Dim RawVersions As List(Of String) = Data("gameVersions").Select(Function(t) t.ToString.Trim.Lower).ToList
.GameVersions = RawVersions.
Where(Function(v) McVersion.IsFormatFit(v)).
Where(Function(v) McVersion.IsStandardFormat(v)).
Select(Function(v) v.Replace("-snapshot", " 预览版")).
Distinct.ToList
If .GameVersions.IsSingle Then
.GameVersions = .GameVersions.ToList
ElseIf .GameVersions.Count > 1 Then
.GameVersions = .GameVersions.SortByComparison(AddressOf CompareVersionGE).ToList
.GameVersions = .GameVersions.SortByComparison(AddressOf McUtils.CompareVersionGE).ToList
If .ResourceType = ResourceTypes.ModPack Then .GameVersions = { .GameVersions(0)}.ToList '整合包理应只 “支持” 一个版本
Else
.GameVersions = New List(Of String) From {"未知版本"}
@@ -176,7 +177,7 @@ Public Class ResourceVersion
.FileName = File("filename")
.DownloadUrls = New List(Of String) From {File("url"), DlSourceModGet(File("url"))}.Distinct.ToList '同时添加了镜像源
.Size = File("size")
.Hash = File("hashes")("sha1")
.Hash = File("hashes")("sha1")?.ToString.Lower()
End If
'类别与 Loaders
'结果可能混杂着 Mod、数据包和服务端插件
@@ -185,7 +186,7 @@ Public Class ResourceVersion
For Each Loader As ModLoaders In EnumUtils.GetAllFlags(Of ModLoaders)()
If RawLoaders.Contains(Loader.ToString.Lower) Then .ModLoaders = .ModLoaders Or Loader
Next
If .ResourceType.HasFlag(ResourceTypes.Mod) OrElse .ResourceType.HasFlag(ResourceTypes.DataPack) Then
If .ResourceType.HasFlagF(ResourceTypes.Mod) OrElse .ResourceType.HasFlagF(ResourceTypes.DataPack) Then
If RawLoaders.Intersect({"bukkit", "folia", "paper", "purpur", "spigot"}).Any() Then .ResourceType = ResourceTypes.Plugin 'Veinminer Enchantment 同时支持服务端与 Fabric
If RawLoaders.Contains("datapack") Then .ResourceType = ResourceTypes.DataPack
If .ModLoaders.Flags().Any() Then .ResourceType = ResourceTypes.Mod
@@ -208,7 +209,7 @@ Public Class ResourceVersion
If .GameVersions.IsSingle Then
'无需处理
ElseIf .GameVersions.Count > 1 Then
.GameVersions = .GameVersions.SortByComparison(AddressOf CompareVersionGE).ToList
.GameVersions = .GameVersions.SortByComparison(AddressOf McUtils.CompareVersionGE).ToList
If .ResourceType = ResourceTypes.ModPack Then .GameVersions = New List(Of String) From { .GameVersions(0)} '整合包理应只 “支持” 一个版本
ElseIf RawVersions.Any(Function(v) v.RegexCheck("[0-9]{2}w[0-9]{2}[a-z]")) Then
.GameVersions = RawVersions.Where(Function(v) v.RegexCheck("[0-9]{2}w[0-9]{2}[a-z]")).ToList
@@ -348,14 +349,14 @@ Public Class ResourceVersion
.Height = 42
.Type = MyListItem.CheckType.Clickable
.Tag = Me
.Title = If(IsBadDisplay, FileName, Display)
.Title = If(IsBadDisplay, FileName, Display).BeforeLastOfAny({".zip", ".jar", ".mrpack", ".litemod"}, True)
.Logo = $"{PathImage}ReleaseTypes/{ReleaseType}.png"
AddHandler .Click, ClickHandler
'描述
.Info =
Iterator Function()
If ModLoaders <> ModLoaders.None Then Yield ModLoaders.Flags.Join("、"c)
If .Title <> FileName.BeforeLast(".") Then Yield FileName.BeforeLast(".")
If .Title <> PathUtils.GetFileNameWithoutExtension(FileName) Then Yield PathUtils.GetFileNameWithoutExtension(FileName)
If Dependencies.Any Then Yield $"{Dependencies.Count} 项前置"
If GameVersions.All(Function(v) Not v.Contains(".") OrElse {"w", "snapshot", "rc", "pre", "experimental", "-"}.Any(Function(s) v.ContainsIgnoreCase(s))) Then Yield $"游戏版本 {GameVersions.Join("、"c)}"
If DownloadCount > 0 Then Yield $"下载 {If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万", DownloadCount.ToString() + " ")}次" 'CurseForge 的下载次数经常错误地返回 0
@@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices
' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
' 方法是按如下所示使用“*”
<Assembly: AssemblyVersion("2.13.0.1")>
<Assembly: AssemblyFileVersion("2.13.0.1")>
<Assembly: AssemblyVersion("2.99.0.0")>
<Assembly: AssemblyFileVersion("2.99.0.0")>
<Assembly: NeutralResourcesLanguage("")>
-20
View File
@@ -140,16 +140,6 @@ Namespace My.Resources
End Get
End Property
'''<summary>
''' 查找 System.Byte[] 类型的本地化资源。
'''</summary>
Friend ReadOnly Property JavaWrapper() As Byte()
Get
Dim obj As Object = ResourceManager.GetObject("JavaWrapper", resourceCulture)
Return CType(obj,Byte())
End Get
End Property
'''<summary>
''' 查找 System.Byte[] 类型的本地化资源。
'''</summary>
@@ -160,16 +150,6 @@ Namespace My.Resources
End Get
End Property
'''<summary>
''' 查找 System.Byte[] 类型的本地化资源。
'''</summary>
Friend ReadOnly Property LUA() As Byte()
Get
Dim obj As Object = ResourceManager.GetObject("LUA", resourceCulture)
Return CType(obj,Byte())
End Get
End Property
'''<summary>
''' 查找 System.Byte[] 类型的本地化资源。
'''</summary>
@@ -142,21 +142,18 @@
<data name="Imazen.WebP" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Imazen.WebP.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="JavaWrapper" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\java-wrapper.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="libwebp64" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\libwebp64.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="LUA" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\lwjgl-unsafe-agent.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="MeloongCore" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\PCLCS\bin\MeloongCore.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="MeloongCore.Wpf" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\PCLCS\bin\MeloongCore.Wpf.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="Microsoft.Win32.Registry" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\..\PCLCS\bin\Microsoft.Win32.Registry.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="NAudio" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\NAudio.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
File diff suppressed because it is too large. Load diff
@@ -1,7 +1,7 @@
<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"
xmlns:local="clr-namespace:PCL" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PageDownloadInstall"
PanScroll="PanBack">
<Grid>
@@ -14,19 +14,96 @@
</StackPanel.RenderTransform>
</StackPanel>
<StackPanel Grid.Row="1" x:Name="PanSelect" Visibility="Collapsed" Opacity="0" IsHitTestVisible="False" Margin="0,0,0,45">
<StackPanel.Resources>
<Style x:Key="ModifyCompareIcon" TargetType="{x:Type local:MyImage}">
<Setter Property="Width" Value="18" />
<Setter Property="Height" Value="18" />
<Setter Property="RenderOptions.BitmapScalingMode" Value="Linear" />
</Style>
<Style x:Key="ModifyCompareText" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource BasedOnTextBlock}">
<Setter Property="Margin" Value="7,0,0,0" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style x:Key="ModifyCompareArrow" TargetType="{x:Type Path}">
<Setter Property="Data" Value="M3,3 L7,7 3,11 M9,3 L13,7 9,11" />
<Setter Property="Margin" Value="10,0,18,0" />
<Setter Property="Height" Value="14" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Stroke" Value="{DynamicResource ColorBrushGray2}" />
<Setter Property="StrokeThickness" Value="1.4" />
<Setter Property="StrokeStartLineCap" Value="Round" />
<Setter Property="StrokeEndLineCap" Value="Round" />
<Setter Property="StrokeLineJoin" Value="Round" />
</Style>
<sys:String x:Key="ModifyRestoreLogo">M9.83,3.17 A1.18,1.18 0 0 1 9.83,4.83 L6.85,7.82 H14.5 A6.68,6.68 0 0 1 14.5,21.18 H11 A1.18,1.18 0 0 1 11,18.82 H14.5 A4.32,4.32 0 0 0 14.5,10.18 H6.85 L9.83,13.17 A1.18,1.18 0 0 1 8.17,14.83 L3.17,9.83 A1.18,1.18 0 0 1 3.17,8.17 L8.17,3.17 A1.18,1.18 0 0 1 9.83,3.17 Z</sys:String>
<sys:String x:Key="ModifyClearLogo">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</sys:String>
<Style x:Key="ModifyRestoreButton" TargetType="{x:Type local:MyIconButton}">
<Setter Property="Visibility" Value="Collapsed" />
<Setter Property="Height" Value="30" />
<Setter Property="Width" Value="30" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Margin" Value="0,5,32,0" />
<Setter Property="ToolTip" Value="撤销更改" />
<Setter Property="Cursor" Value="Hand" />
</Style>
<Style x:Key="ModifyClearButton" TargetType="{x:Type local:MyIconButton}">
<Setter Property="Height" Value="30" />
<Setter Property="Width" Value="30" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Margin" Value="0,5,32,0" />
<Setter Property="ToolTip" Value="取消安装" />
</Style>
</StackPanel.Resources>
<StackPanel.RenderTransform>
<TranslateTransform X="40" />
</StackPanel.RenderTransform>
<local:MyCard Margin="0,15,0,15" UseAnimation="False" Height="62">
<local:MyCard x:Name="CardSelectName" Margin="0,15,0,15" MinHeight="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" Logo="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" />
<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:MyImage x:Name="ImgLogo" Margin="52,15,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Height="32" RenderOptions.BitmapScalingMode="HighQuality" />
<local:MyTextBox Margin="93,16,16,0" Padding="-1,0" MinHeight="30" FontSize="15" VerticalAlignment="Top" x:Name="TextSelectName" MaxLength="70" HasBackground="False" ShowValidateResult="False" />
<TextBlock x:Name="LabSelectName" Visibility="Collapsed" Height="21" Margin="56,47,42,11" Padding="0,4,0,0" VerticalAlignment="Top" FontSize="12.5" Foreground="{DynamicResource ColorBrushGray3}" TextTrimming="CharacterEllipsis" />
<local:MyIconButton x:Name="BtnOriginalNameRestore" Style="{StaticResource ModifyRestoreButton}" Margin="0,16,6,0"
Theme="Black" LogoScale="0.55" Logo="{StaticResource ModifyRestoreLogo}" />
</local:MyCard>
<local:MyHint x:Name="HintSelectName" Theme="Red" Visibility="Collapsed" />
<local:MyHint Text="如果不安装 Fabric API,大多数 Mod 都会无法使用!" Margin="0,10,0,0" x:Name="HintFabricAPI" Theme="Red" />
<local:MyHint Text="必须安装 OptiFabric 才能正常使用 OptiFine!" Margin="0,10,0,0" x:Name="HintOptiFabric" Theme="Red" />
<local:MyHint Text="安装结束后,请在 Mod 下载中搜索 OptiFabric Origins 并下载,否则 OptiFine 会无法使用!" Margin="0,10,0,0" x:Name="HintOptiFabricOld" Theme="Yellow" />
<local:MyHint Text="OptiFine 与一部分 Mod 的兼容性不佳,请谨慎安装。" Margin="0,10,0,0" x:Name="HintModOptiFine" Theme="Yellow" />
<local:MyCard Title="Minecraft" Height="40" Margin="0,12,0,0" x:Name="CardMinecraft" Visibility="Collapsed">
<Grid x:Name="PanMinecraftInfo" Height="18" Margin="132,11,35,0" VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /><ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<local:MyImage x:Name="ImgMinecraft" Height="18" Width="18" Margin="0,0,7,0" RenderOptions.BitmapScalingMode="Linear" />
<TextBlock x:Name="LabMinecraftVersion" Grid.Column="1" VerticalAlignment="Center" TextTrimming="CharacterEllipsis" />
</Grid>
<Grid x:Name="PanMinecraftCompare" Height="18" Margin="132,11,80,0" VerticalAlignment="Top" Visibility="Collapsed" IsHitTestVisible="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /><ColumnDefinition Width="Auto" /><ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<DockPanel>
<local:MyImage x:Name="ImgMinecraftOld" Style="{StaticResource ModifyCompareIcon}" DockPanel.Dock="Left" />
<TextBlock x:Name="LabMinecraftOld" Style="{StaticResource ModifyCompareText}" />
</DockPanel>
<Path Grid.Column="1" Style="{StaticResource ModifyCompareArrow}" />
<DockPanel Grid.Column="2">
<local:MyImage x:Name="ImgMinecraftNew" Style="{StaticResource ModifyCompareIcon}" DockPanel.Dock="Left" />
<TextBlock x:Name="LabMinecraftNew" Style="{StaticResource ModifyCompareText}" />
</DockPanel>
</Grid>
<Grid Height="40" VerticalAlignment="Top" Background="Transparent" Cursor="Hand">
<Path HorizontalAlignment="Right" Margin="0,0,16,0" Stretch="Uniform" Height="6" Width="10" Fill="{StaticResource ColorBrush1}" RenderTransformOrigin="0.5,0.5">
<Path.Data>M2,4 l-2,2 10,10 10,-10 -2,-2 -8,8 -8,-8 z</Path.Data>
<Path.RenderTransform><RotateTransform Angle="270" /></Path.RenderTransform>
</Path>
</Grid>
<local:MyIconButton x:Name="BtnMinecraftRestore" Style="{StaticResource ModifyRestoreButton}" Theme="Black" LogoScale="0.55" Logo="{StaticResource ModifyRestoreLogo}" />
</local:MyCard>
<local:MyCard Title="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">
@@ -37,13 +114,25 @@
<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" />
<local:MyImage 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 x:Name="PanForgeCompare" Height="18" Margin="132,11,80,0" VerticalAlignment="Top" Visibility="Collapsed" IsHitTestVisible="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /><ColumnDefinition Width="Auto" /><ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<DockPanel>
<local:MyImage Style="{StaticResource ModifyCompareIcon}" Source="pack://application:,,,/images/Blocks/Anvil.png" DockPanel.Dock="Left" />
<TextBlock x:Name="LabForgeOld" Style="{StaticResource ModifyCompareText}" />
</DockPanel>
<Path Grid.Column="1" Style="{StaticResource ModifyCompareArrow}" />
<DockPanel Grid.Column="2">
<local:MyImage Style="{StaticResource ModifyCompareIcon}" Source="pack://application:,,,/images/Blocks/Anvil.png" DockPanel.Dock="Left" />
<TextBlock Style="{StaticResource ModifyCompareText}" />
</DockPanel>
</Grid>
<local:MyIconButton x:Name="BtnForgeClear" Style="{StaticResource ModifyClearButton}" Theme="Black" LogoScale="0.5" Logo="{StaticResource ModifyClearLogo}" />
<local:MyIconButton x:Name="BtnForgeRestore" Style="{StaticResource ModifyRestoreButton}" Theme="Black" LogoScale="0.55" Logo="{StaticResource ModifyRestoreLogo}" />
</local:MyCard>
<local:MyCard Title="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" />
@@ -55,13 +144,25 @@
<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" />
<local:MyImage 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 x:Name="PanNeoForgeCompare" Height="18" Margin="132,11,80,0" VerticalAlignment="Top" Visibility="Collapsed" IsHitTestVisible="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /><ColumnDefinition Width="Auto" /><ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<DockPanel>
<local:MyImage Style="{StaticResource ModifyCompareIcon}" Source="pack://application:,,,/images/Blocks/NeoForge.png" DockPanel.Dock="Left" />
<TextBlock x:Name="LabNeoForgeOld" Style="{StaticResource ModifyCompareText}" />
</DockPanel>
<Path Grid.Column="1" Style="{StaticResource ModifyCompareArrow}" />
<DockPanel Grid.Column="2">
<local:MyImage Style="{StaticResource ModifyCompareIcon}" Source="pack://application:,,,/images/Blocks/NeoForge.png" DockPanel.Dock="Left" />
<TextBlock Style="{StaticResource ModifyCompareText}" />
</DockPanel>
</Grid>
<local:MyIconButton x:Name="BtnNeoForgeClear" Style="{StaticResource ModifyClearButton}" Theme="Black" LogoScale="0.5" Logo="{StaticResource ModifyClearLogo}" />
<local:MyIconButton x:Name="BtnNeoForgeRestore" Style="{StaticResource ModifyRestoreButton}" Theme="Black" LogoScale="0.55" Logo="{StaticResource ModifyRestoreLogo}" />
</local:MyCard>
<local:MyCard Title="Fabric" Height="40" Margin="0,12,0,0" x:Name="CardFabric" IsSwapped="True" CanSwap="True" SwapLogoRight="True">
<StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanFabric" />
@@ -73,13 +174,25 @@
<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" />
<local:MyImage 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 x:Name="PanFabricCompare" Height="18" Margin="132,11,80,0" VerticalAlignment="Top" Visibility="Collapsed" IsHitTestVisible="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /><ColumnDefinition Width="Auto" /><ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<DockPanel>
<local:MyImage Style="{StaticResource ModifyCompareIcon}" Source="pack://application:,,,/images/Blocks/Fabric.png" DockPanel.Dock="Left" />
<TextBlock x:Name="LabFabricOld" Style="{StaticResource ModifyCompareText}" />
</DockPanel>
<Path Grid.Column="1" Style="{StaticResource ModifyCompareArrow}" />
<DockPanel Grid.Column="2">
<local:MyImage Style="{StaticResource ModifyCompareIcon}" Source="pack://application:,,,/images/Blocks/Fabric.png" DockPanel.Dock="Left" />
<TextBlock Style="{StaticResource ModifyCompareText}" />
</DockPanel>
</Grid>
<local:MyIconButton x:Name="BtnFabricClear" Style="{StaticResource ModifyClearButton}" Theme="Black" LogoScale="0.5" Logo="{StaticResource ModifyClearLogo}" />
<local:MyIconButton x:Name="BtnFabricRestore" Style="{StaticResource ModifyRestoreButton}" Theme="Black" LogoScale="0.55" Logo="{StaticResource ModifyRestoreLogo}" />
</local:MyCard>
<local:MyCard Title="Fabric API" 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" />
@@ -91,13 +204,25 @@
<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" />
<local:MyImage 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 x:Name="PanFabricApiCompare" Height="18" Margin="132,11,80,0" VerticalAlignment="Top" Visibility="Collapsed" IsHitTestVisible="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /><ColumnDefinition Width="Auto" /><ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<DockPanel>
<local:MyImage Style="{StaticResource ModifyCompareIcon}" Source="pack://application:,,,/images/Blocks/Fabric.png" DockPanel.Dock="Left" />
<TextBlock x:Name="LabFabricApiOld" Style="{StaticResource ModifyCompareText}" />
</DockPanel>
<Path Grid.Column="1" Style="{StaticResource ModifyCompareArrow}" />
<DockPanel Grid.Column="2">
<local:MyImage Style="{StaticResource ModifyCompareIcon}" Source="pack://application:,,,/images/Blocks/Fabric.png" DockPanel.Dock="Left" />
<TextBlock Style="{StaticResource ModifyCompareText}" />
</DockPanel>
</Grid>
<local:MyIconButton x:Name="BtnFabricApiClear" Style="{StaticResource ModifyClearButton}" Theme="Black" LogoScale="0.5" Logo="{StaticResource ModifyClearLogo}" />
<local:MyIconButton x:Name="BtnFabricApiRestore" Style="{StaticResource ModifyRestoreButton}" Theme="Black" LogoScale="0.55" Logo="{StaticResource ModifyRestoreLogo}" />
</local:MyCard>
<local:MyCard Title="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">
@@ -111,13 +236,25 @@
<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" />
<local:MyImage 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 x:Name="PanOptiFineCompare" Height="18" Margin="132,11,80,0" VerticalAlignment="Top" Visibility="Collapsed" IsHitTestVisible="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /><ColumnDefinition Width="Auto" /><ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<DockPanel>
<local:MyImage Style="{StaticResource ModifyCompareIcon}" Source="pack://application:,,,/images/Blocks/GrassPath.png" DockPanel.Dock="Left" />
<TextBlock x:Name="LabOptiFineOld" Style="{StaticResource ModifyCompareText}" />
</DockPanel>
<Path Grid.Column="1" Style="{StaticResource ModifyCompareArrow}" />
<DockPanel Grid.Column="2">
<local:MyImage Style="{StaticResource ModifyCompareIcon}" Source="pack://application:,,,/images/Blocks/GrassPath.png" DockPanel.Dock="Left" />
<TextBlock Style="{StaticResource ModifyCompareText}" />
</DockPanel>
</Grid>
<local:MyIconButton x:Name="BtnOptiFineClear" Style="{StaticResource ModifyClearButton}" Theme="Black" LogoScale="0.5" Logo="{StaticResource ModifyClearLogo}" />
<local:MyIconButton x:Name="BtnOptiFineRestore" Style="{StaticResource ModifyRestoreButton}" Theme="Black" LogoScale="0.55" Logo="{StaticResource ModifyRestoreLogo}" />
</local:MyCard>
<local:MyCard Title="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" />
@@ -129,13 +266,25 @@
<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" />
<local:MyImage 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 x:Name="PanOptiFabricCompare" Height="18" Margin="132,11,80,0" VerticalAlignment="Top" Visibility="Collapsed" IsHitTestVisible="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /><ColumnDefinition Width="Auto" /><ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<DockPanel>
<local:MyImage Style="{StaticResource ModifyCompareIcon}" Source="pack://application:,,,/images/Blocks/OptiFabric.png" DockPanel.Dock="Left" />
<TextBlock x:Name="LabOptiFabricOld" Style="{StaticResource ModifyCompareText}" />
</DockPanel>
<Path Grid.Column="1" Style="{StaticResource ModifyCompareArrow}" />
<DockPanel Grid.Column="2">
<local:MyImage Style="{StaticResource ModifyCompareIcon}" Source="pack://application:,,,/images/Blocks/OptiFabric.png" DockPanel.Dock="Left" />
<TextBlock Style="{StaticResource ModifyCompareText}" />
</DockPanel>
</Grid>
<local:MyIconButton x:Name="BtnOptiFabricClear" Style="{StaticResource ModifyClearButton}" Theme="Black" LogoScale="0.5" Logo="{StaticResource ModifyClearLogo}" />
<local:MyIconButton x:Name="BtnOptiFabricRestore" Style="{StaticResource ModifyRestoreButton}" Theme="Black" LogoScale="0.55" Logo="{StaticResource ModifyRestoreLogo}" />
</local:MyCard>
<local:MyCard Title="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" />
@@ -147,13 +296,25 @@
<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" />
<local:MyImage 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 x:Name="PanLiteLoaderCompare" Height="18" Margin="132,11,80,0" VerticalAlignment="Top" Visibility="Collapsed" IsHitTestVisible="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /><ColumnDefinition Width="Auto" /><ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<DockPanel>
<local:MyImage Style="{StaticResource ModifyCompareIcon}" Source="pack://application:,,,/images/Blocks/Egg.png" DockPanel.Dock="Left" />
<TextBlock x:Name="LabLiteLoaderOld" Style="{StaticResource ModifyCompareText}" />
</DockPanel>
<Path Grid.Column="1" Style="{StaticResource ModifyCompareArrow}" />
<DockPanel Grid.Column="2">
<local:MyImage Style="{StaticResource ModifyCompareIcon}" Source="pack://application:,,,/images/Blocks/Egg.png" DockPanel.Dock="Left" />
<TextBlock Style="{StaticResource ModifyCompareText}" />
</DockPanel>
</Grid>
<local:MyIconButton x:Name="BtnLiteLoaderClear" Style="{StaticResource ModifyClearButton}" Theme="Black" LogoScale="0.5" Logo="{StaticResource ModifyClearLogo}" />
<local:MyIconButton x:Name="BtnLiteLoaderRestore" Style="{StaticResource ModifyRestoreButton}" Theme="Black" LogoScale="0.55" Logo="{StaticResource ModifyRestoreLogo}" />
</local:MyCard>
</StackPanel>
</Grid>
@@ -163,7 +324,7 @@
LogoScale="0.95" Logo="M955 610h-59c-15 0-29 13-29 29v196c0 15-13 29-29 29h-649c-15 0-29-13-29-29v-196c0-15-13-29-29-29h-59c-15 0-29 13-29 29V905c0 43 35 78 78 78h787c43 0 78-35 78-78V640c0-15-13-29-29-29zM492 740c11 11 29 11 41 0l265-265c11-11 11-29 0-41l-41-41c-11-11-29-11-41 0l-110 110c-11 11-33 3-33-13V68C571 53 555 39 541 39h-59c-15 0-29 13-29 29v417c0 17-21 25-33 13l-110-110c-11-11-29-11-41 0L226 433c-11 11-11 29 0 41L492 740z" />
</Grid>
<local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40,0" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False">
<local:MyLoading Text="正在获取版本列表" Margin="20,20,20,17" x:Name="LoadMinecraft" HorizontalAlignment="Center" VerticalAlignment="Center" />
<local:MyLoading Text="正在加载" Margin="20,20,20,17" x:Name="LoadMinecraft" HorizontalAlignment="Center" VerticalAlignment="Center" ShowProgress="True" />
<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" />
@@ -1,12 +1,34 @@
Public Class PageDownloadInstall
Private Sub LoaderInit() Handles Me.Initialized
Public Sub ControlInit()
DisabledPageAnimControls.Add(BtnStart)
LoadOptiFine.State = DlOptiFineListLoader
LoadLiteLoader.State = DlLiteLoaderListLoader
LoadFabric.State = DlFabricListLoader
LoadFabricApi.State = DlFabricApiLoader
LoadNeoForge.State = DlNeoForgeListLoader
LoadOptiFabric.State = DlOptiFabricLoader
McDownloadForgeRecommendedRefresh()
End Sub
Public Overridable Sub LoaderInit() Handles Me.Initialized
ControlInit()
If RestrictMcDownload Then
PageLoaderInit(LoadMinecraft, PanLoad, PanAllBack, Nothing, New LoaderTask(Of String, DlClientListResult)("关闭下载", Sub() Throw New Exception("为遵守相关法律法规,PCL 暂时不再提供 MC 下载功能,非常抱歉 qwq……")), AddressOf LoadMinecraft_OnFinish)
Else
PageLoaderInit(LoadMinecraft, PanLoad, PanAllBack, Nothing, DlClientListLoader, AddressOf LoadMinecraft_OnFinish)
End If
End Sub
Private IsLoad As Boolean = False
Private Sub Init() Handles Me.Loaded
Public Overridable Sub Init() Handles Me.Loaded
If IsLoad Then
IsSelectNameEdited = False
ExitSelectPage()
If DlClientListLoader.State = LoadState.Finished Then TrySelectWaitingVersion()
End If
IsLoad = True
PanBack.ScrollToHome()
DlOptiFineListLoader.Start()
DlLiteLoaderListLoader.Start()
@@ -17,35 +39,19 @@ Public Class PageDownloadInstall
TextSelectName.ValidateRules = New ObjectModel.Collection(Of Validate) From {New ValidateFolderName(McFolderSelected & "versions")}
TextSelectName.Validate()
ReloadSelected()
'非重复加载部分
If IsLoad Then Return
IsLoad = True
McDownloadForgeRecommendedRefresh()
LoadOptiFine.State = DlOptiFineListLoader
LoadLiteLoader.State = DlLiteLoaderListLoader
LoadFabric.State = DlFabricListLoader
LoadFabricApi.State = DlFabricApiLoader
LoadNeoForge.State = DlNeoForgeListLoader
LoadOptiFabric.State = DlOptiFabricLoader
End Sub
#Region "页面切换"
'页面切换动画
Public IsInSelectPage As Boolean = False
Private IsFirstLoaded As Boolean = False
Private Sub EnterSelectPage()
If IsInSelectPage Then Return
''' <summary>
''' 选择页面 UI 初始化。
''' </summary>
Public Sub PrepareSelectPage()
IsInSelectPage = True
PanInner.Margin = New Thickness(25, 10, 25, 40)
AutoSelectedFabricApi = False
AutoSelectedOptiFabric = False
IsSelectNameEdited = False
PanSelect.Visibility = Visibility.Visible
PanSelect.IsHitTestVisible = True
PanMinecraft.IsHitTestVisible = False
@@ -54,13 +60,9 @@ Public Class PageDownloadInstall
DisabledPageAnimControls.Remove(BtnStart)
BtnStart.Show = True
CardOptiFine.IsSwapped = True
CardLiteLoader.IsSwapped = True
CardForge.IsSwapped = True
CardNeoForge.IsSwapped = True
CardFabric.IsSwapped = True
CardFabricApi.IsSwapped = True
CardOptiFabric.IsSwapped = True
For Each Card In {CardOptiFine, CardLiteLoader, CardForge, CardNeoForge, CardFabric, CardFabricApi, CardOptiFabric}
Card.IsSwapped = True
Next
'如果在选择页面按了刷新键,选择页的东西可能会由于动画被隐藏,但不会由于加载结束而再次显示,因此这里需要手动恢复
For Each Control In GetAllAnimControls(PanSelect)
@@ -69,9 +71,16 @@ Public Class PageDownloadInstall
Control.RenderTransform = New TranslateTransform
End If
Next
End Sub
Private Sub EnterSelectPage()
If IsInSelectPage Then Return
AutoSelectedFabricApi = False
AutoSelectedOptiFabric = False
PrepareSelectPage()
'启动 Forge 加载
If McVersion.IsFormatFit(VanillaName) Then
If McVersion.IsStandardFormat(VanillaName) Then
Dim ForgeLoader = New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion " & VanillaName, AddressOf DlForgeVersionMain)
LoadForge.State = ForgeLoader
ForgeLoader.Start(VanillaName)
@@ -87,6 +96,16 @@ Public Class PageDownloadInstall
AaCode(
Sub()
PanBack.ScrollToHome()
ReloadComponentLists()
PanMinecraft.Visibility = Visibility.Collapsed
End Sub, After:=True),
AaOpacity(PanSelect, 1 - PanSelect.Opacity, 70, 100),
AaTranslateX(PanSelect, -CType(PanSelect.RenderTransform, TranslateTransform).X, 160, 100, Ease:=New AniEaseOutFluent(AniEasePower.ExtraStrong)),
AaCode(Sub() PanBack.IsHitTestVisible = True,, True)
}, "FrmDownloadInstall SelectPageSwitch", True)
End Sub
Public Sub ReloadComponentLists()
TextSelectName.Validate()
OptiFine_Loaded()
LiteLoader_Loaded()
@@ -96,26 +115,8 @@ Public Class PageDownloadInstall
FabricApi_Loaded()
OptiFabric_Loaded()
ReloadSelected()
PanMinecraft.Visibility = Visibility.Collapsed
End Sub, After:=True),
AaOpacity(PanSelect, 1 - PanSelect.Opacity, 70, 100),
AaTranslateX(PanSelect, -CType(PanSelect.RenderTransform, TranslateTransform).X, 160, 100, Ease:=New AniEaseOutFluent(AniEasePower.ExtraStrong)),
AaCode(
Sub()
PanBack.IsHitTestVisible = True
'初始化 Binding
If IsFirstLoaded Then Return
IsFirstLoaded = True
BtnOptiFineClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardOptiFine.MainTextBlock, .Mode = BindingMode.OneWay})
BtnLiteLoaderClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardLiteLoader.MainTextBlock, .Mode = BindingMode.OneWay})
BtnForgeClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardForge.MainTextBlock, .Mode = BindingMode.OneWay})
BtnNeoForgeClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardNeoForge.MainTextBlock, .Mode = BindingMode.OneWay})
BtnFabricClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardFabric.MainTextBlock, .Mode = BindingMode.OneWay})
BtnFabricApiClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardFabricApi.MainTextBlock, .Mode = BindingMode.OneWay})
BtnOptiFabricClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardOptiFabric.MainTextBlock, .Mode = BindingMode.OneWay})
End Sub,, True)
}, "FrmDownloadInstall SelectPageSwitch", True)
End Sub
Public Sub ExitSelectPage() Handles BtnBack.Click
If Not IsInSelectPage Then Return
IsInSelectPage = False
@@ -144,10 +145,11 @@ Public Class PageDownloadInstall
End Sub,, True)
}, "FrmDownloadInstall SelectPageSwitch")
End Sub
Public Sub MinecraftSelected(sender As MyListItem, e As MouseButtonEventArgs)
Public Overridable Sub MinecraftSelected(sender As MyListItem, e As MouseButtonEventArgs)
VanillaName = sender.Title
VanillaData = sender.Tag
VanillaIcon = sender.Logo
LabMinecraftVersion.Text = VanillaName
EnterSelectPage()
End Sub
@@ -156,9 +158,9 @@ Public Class PageDownloadInstall
#Region "选择"
'Minecraft
Private VanillaName As String
Private VanillaData As JObject
Private VanillaIcon As String
Public VanillaName As String
Public VanillaData As JObject
Public VanillaIcon As String
Private ReadOnly Property VanillaDrop As Integer
Get
Return McVersion.VersionToDrop(VanillaName)
@@ -166,24 +168,25 @@ Public Class PageDownloadInstall
End Property
'附加组件
Private SelectedOptiFine As DlOptiFineListEntry = Nothing
Private SelectedLiteLoader As DlLiteLoaderListEntry = Nothing
Private SelectedForge As DlForgeVersionEntry = Nothing
Private SelectedNeoForge As DlNeoForgeListEntry = Nothing
Private SelectedFabric As String = Nothing
Private SelectedFabricApi As ResourceVersion = Nothing
Private SelectedOptiFabric As ResourceVersion = Nothing
Public SelectedOptiFine As DlOptiFineListEntry = Nothing
Public SelectedLiteLoader As DlLiteLoaderListEntry = Nothing
Public SelectedForge As DlForgeVersionEntry = Nothing
Public SelectedNeoForge As DlNeoForgeListEntry = Nothing
Public SelectedFabric As String = Nothing
Public SelectedFabricApi As ResourceVersion = Nothing
Public SelectedOptiFabric As ResourceVersion = Nothing
''' <summary>
''' 重载已选择的项目的显示。
''' </summary>
Private Sub ReloadSelected() Handles CardOptiFine.Swap, LoadOptiFine.StateChanged, CardForge.Swap, LoadForge.StateChanged, CardNeoForge.Swap, LoadNeoForge.StateChanged, CardFabric.Swap, LoadFabric.StateChanged, CardFabricApi.Swap, LoadFabricApi.StateChanged, CardOptiFabric.Swap, LoadOptiFabric.StateChanged, CardLiteLoader.Swap, LoadLiteLoader.StateChanged
Public Overridable Sub ReloadSelected() Handles CardOptiFine.Swap, LoadOptiFine.StateChanged, CardForge.Swap, LoadForge.StateChanged, CardNeoForge.Swap, LoadNeoForge.StateChanged, CardFabric.Swap, LoadFabric.StateChanged, CardFabricApi.Swap, LoadFabricApi.StateChanged, CardOptiFabric.Swap, LoadOptiFabric.StateChanged, CardLiteLoader.Swap, LoadLiteLoader.StateChanged
Static Ongoing As Boolean = False '#3742 中,LoadOptiFineGetError 会初始化 LoadOptiFine,触发事件 LoadOptiFine.StateChanged,导致再次调用 SelectReload
If VanillaName Is Nothing OrElse Ongoing Then Return
Ongoing = True
'主预览
SelectNameUpdate()
ImgLogo.Source = GetSelectLogo()
ImgMinecraft.Source = VanillaIcon
'OptiFine
Dim OptiFineError As String = LoadOptiFineGetError()
CardOptiFine.MainSwap.Visibility = If(OptiFineError Is Nothing, Visibility.Visible, Visibility.Collapsed)
@@ -222,7 +225,7 @@ Public Class PageDownloadInstall
End If
End If
'Forge
If Not McVersion.IsFormatFit(VanillaName) Then
If Not McVersion.IsStandardFormat(VanillaName) Then
CardForge.Visibility = Visibility.Collapsed
Else
CardForge.Visibility = Visibility.Visible
@@ -356,7 +359,7 @@ Public Class PageDownloadInstall
''' <summary>
''' 清空已选择的项目。
''' </summary>
Private Sub ClearSelected()
Public Sub ClearSelected()
VanillaName = Nothing
VanillaData = Nothing
VanillaIcon = Nothing
@@ -367,7 +370,10 @@ Public Class PageDownloadInstall
SelectedFabric = Nothing
SelectedFabricApi = Nothing
SelectedOptiFabric = Nothing
IsSelectNameEdited = False
For Each ClearButton In {BtnOptiFineClear, BtnLiteLoaderClear, BtnForgeClear, BtnNeoForgeClear,
BtnFabricClear, BtnFabricApiClear, BtnOptiFabricClear}
ClearButton.Visibility = Visibility.Collapsed
Next
End Sub
'信息栏动画
Private Sub SetPanelVisibility(Panel As Grid, IsVisible As Boolean)
@@ -389,7 +395,7 @@ Public Class PageDownloadInstall
''' <summary>
''' 获取版本图标。
''' </summary>
Private Function GetSelectLogo() As String
Public Overridable Function GetSelectLogo() As String
If SelectedFabric IsNot Nothing Then
Return "pack://application:,,,/images/Blocks/Fabric.png"
ElseIf SelectedForge IsNot Nothing Then
@@ -409,7 +415,7 @@ Public Class PageDownloadInstall
''' <summary>
''' 获取默认版本名。
''' </summary>
Private Function GetSelectName() As String
Public Function GetSelectName() As String
Dim Name As String = VanillaName
If SelectedFabric IsNot Nothing Then Name += "-Fabric " & SelectedFabric.BeforeFirst("+")
If SelectedForge IsNot Nothing Then Name += "-Forge_" & SelectedForge.VersionName
@@ -418,36 +424,39 @@ Public Class PageDownloadInstall
If SelectedOptiFine IsNot Nothing Then Name += "-OptiFine_" & SelectedOptiFine.DisplayName.Replace(VanillaName & " ", "").Replace(" ", "_")
Return Name
End Function
Private IsSelectNameEdited As Boolean = False
Private IsSelectNameChanging As Boolean = False
Public IsSelectNameEdited As Boolean = False
Private Sub SelectNameUpdate()
If IsSelectNameEdited OrElse IsSelectNameChanging Then Return
IsSelectNameChanging = True
If IsSelectNameEdited Then Return
TextSelectName.Text = GetSelectName()
IsSelectNameChanging = False
IsSelectNameEdited = False
End Sub
Private Sub TextSelectName_TextChanged(sender As Object, e As TextChangedEventArgs) Handles TextSelectName.TextChanged
If IsSelectNameChanging Then Return
IsSelectNameEdited = True
ReloadSelected()
End Sub
Private Sub TextSelectName_ValidateChanged(sender As Object, e As EventArgs) Handles TextSelectName.ValidateChanged
BtnStart.IsEnabled = TextSelectName.IsValidated
HintSelectName.Text = TextSelectName.ValidateResult
HintSelectName.Visibility = If(TextSelectName.IsValidated, Visibility.Collapsed, Visibility.Visible)
End Sub
#End Region
#Region "加载器"
Public MinecraftVersions As JArray
Private Sub LoadMinecraft_OnFinish()
MinecraftVersions = CType(DlClientListLoader.Output.Value("versions").DeepClone(), JArray)
ShowMinecraftVersionList()
TrySelectWaitingVersion() '自动选择版本
End Sub
'结果数据化
Private Sub LoadMinecraft_OnFinish()
Public Overridable Sub ShowMinecraftVersionList()
ExitSelectPage() '返回
Try
Dim Dict As New Dictionary(Of String, List(Of JObject)) From {
{"正式版", New List(Of JObject)}, {"预览版", New List(Of JObject)}, {"远古版", New List(Of JObject)}, {"愚人节版", New List(Of JObject)}
}
Dim Versions As JArray = DlClientListLoader.Output.Value("versions")
For Each Version As JObject In Versions
For Each Version As JObject In MinecraftVersions
'确定分类
Dim Type As String = Version("type")
Select Case Type
@@ -498,7 +507,7 @@ Public Class PageDownloadInstall
'清空当前
PanMinecraft.Children.Clear()
'添加最新版本
Dim CardInfo As New MyCard With {.Title = "最新版本", .Margin = New Thickness(0, 15, 0, 15), .SwapType = 2}
Dim CardInfo As New MyCard With {.Title = "最新版本", .Margin = New Thickness(0, 15, 0, 15)}
Dim PinnedVersions As New List(Of JObject)
Dim Release As JObject = Dict("正式版")(0).DeepClone()
Release("lore") = "最新正式版,发布于 " & Release("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm")
@@ -508,36 +517,41 @@ Public Class PageDownloadInstall
Snapshot("lore") = "最新预览版,发布于 " & Snapshot("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm")
PinnedVersions.Add(Snapshot)
End If
Dim PanInfo As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = PinnedVersions}
MyCard.StackInstall(PanInfo, 7)
Dim PanInfo As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0)}
CardInfo.Children.Add(PanInfo)
CardInfo.SwapControl = PanInfo
CardInfo.SetContentBuilder(PinnedVersions, '使用 Lambda 转发点击事件,避免 #535
Function(Version) McDownloadListItem(Version, Sub(sender, e) MinecraftSelected(sender, e), False))
CardInfo.BuildContent()
PanMinecraft.Children.Insert(0, CardInfo)
'添加其他版本
For Each Pair As KeyValuePair(Of String, List(Of JObject)) In Dict
If Not Pair.Value.Any() Then Continue For
'增加卡片
Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15), .SwapType = 7}
Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value}
Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15)}
Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0)}
NewCard.Children.Add(NewStack)
NewCard.SwapControl = NewStack
NewCard.SetContentBuilder(Pair.Value,
Function(Version) McDownloadListItem(Version, Sub(sender, e) MinecraftSelected(sender, e), False))
NewCard.IsSwapped = True
PanMinecraft.Children.Add(NewCard)
Next
'自动选择版本
If VersionWaitingSelect Is Nothing Then Exit Try
Logger.Info($"自动选择 MC 版本:{VersionWaitingSelect}")
For Each Version As JObject In Versions
If Version("id").ToString <> VersionWaitingSelect Then Continue For
Dim Item = McDownloadListItem(Version, Sub()
End Sub, False).Init()
MinecraftSelected(Item, Nothing)
VersionWaitingSelect = Nothing
Return
Next
Catch ex As Exception
Logger.Error(ex, "可视化安装版本列表出错")
End Try
End Sub
Private Sub TrySelectWaitingVersion()
If VersionWaitingSelect Is Nothing Then Return
For Each Version As JObject In MinecraftVersions
If Version("id").ToString <> VersionWaitingSelect Then Continue For
Logger.Info($"自动选择 MC 版本:{VersionWaitingSelect}")
MinecraftSelected(McDownloadListItem(Version, Nothing, False).Init(), Nothing)
VersionWaitingSelect = Nothing
Return
Next
End Sub
''' <summary>
''' 当 MC 版本列表加载完时,立即自动选择的版本。用于外部调用。
''' </summary>
@@ -550,16 +564,16 @@ Public Class PageDownloadInstall
''' <summary>
''' 获取 OptiFine 的加载异常信息。若正常则返回 Nothing。
''' </summary>
Private Function LoadOptiFineGetError() As String
Public Function LoadOptiFineGetError() As String
If SelectedNeoForge IsNot Nothing Then Return "与 NeoForge 不兼容"
'检查 Forge 1.13 - 1.14.3:全部不兼容
If SelectedForge IsNot Nothing AndAlso
CompareVersion(VanillaName, "1.13") >= 0 AndAlso CompareVersion("1.14.3", VanillaName) >= 0 Then
McUtils.CompareVersionGE(VanillaName, "1.13") AndAlso McUtils.CompareVersionGE("1.14.3", VanillaName) Then
Return "与 Forge 不兼容"
End If
'检查 Fabric 1.20.5+:全部不兼容
If SelectedFabric IsNot Nothing AndAlso
CompareVersion(VanillaName, "1.20.4") > 0 Then
McUtils.CompareVersion(VanillaName, "1.20.4") > 0 Then
Return "与 Fabric 不兼容"
End If
'检查 Loader
@@ -584,12 +598,12 @@ Public Class PageDownloadInstall
End Function
'检查某个 OptiFine 是否与某个 Forge 兼容
Private Function IsOptiFineSuitForForge(OptiFine As DlOptiFineListEntry, Forge As DlForgeVersionEntry)
Public Shared Function IsOptiFineSuitForForge(OptiFine As DlOptiFineListEntry, Forge As DlForgeVersionEntry) As Boolean
If Forge.Inherit <> OptiFine.Inherit Then Return False '不是同一个大版本
If OptiFine.RequiredForgeVersion Is Nothing Then Return False '不兼容 Forge
If String.IsNullOrWhiteSpace(OptiFine.RequiredForgeVersion) Then Return True '#4183
If OptiFine.RequiredForgeVersion.Contains(".") Then 'XX.X.XXX
Return CompareVersion(Forge.Version.ToString, OptiFine.RequiredForgeVersion) = 0
Return McUtils.CompareVersion(Forge.Version.ToString, OptiFine.RequiredForgeVersion) = 0
Else 'XXXX
Return Forge.Version.Revision = OptiFine.RequiredForgeVersion
End If
@@ -619,7 +633,7 @@ Public Class PageDownloadInstall
Function(Left As DlOptiFineListEntry, Right As DlOptiFineListEntry) As Boolean
If Not Left.IsPreview AndAlso Right.IsPreview Then Return True
If Left.IsPreview AndAlso Not Right.IsPreview Then Return False
Return CompareVersionGE(Left.DisplayName, Right.DisplayName)
Return McUtils.CompareVersionGE(Left.DisplayName, Right.DisplayName)
End Function)
'可视化
PanOptiFine.Children.Clear()
@@ -641,12 +655,12 @@ Public Class PageDownloadInstall
CardOptiFine.IsSwapped = True
ReloadSelected()
End Sub
Private Sub OptiFine_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFineClear.MouseLeftButtonUp
Private Sub OptiFine_Clear(sender As Object, e As EventArgs) Handles BtnOptiFineClear.Click
SelectedOptiFine = Nothing
SelectedOptiFabric = Nothing
BtnOptiFabricClear.Visibility = Visibility.Collapsed
AutoSelectedOptiFabric = False
CardOptiFine.IsSwapped = True
e.Handled = True
Forge_Loaded()
NeoForge_Loaded()
ReloadSelected()
@@ -659,7 +673,7 @@ Public Class PageDownloadInstall
''' <summary>
''' 获取 LiteLoader 的加载异常信息。若正常则返回 Nothing。
''' </summary>
Private Function LoadLiteLoaderGetError() As String
Public Function LoadLiteLoaderGetError() As String
'检查 Loader
If GetLoaderError(LoadLiteLoader) IsNot Nothing Then Return GetLoaderError(LoadLiteLoader)
'检查版本
@@ -699,10 +713,9 @@ Public Class PageDownloadInstall
CardLiteLoader.IsSwapped = True
ReloadSelected()
End Sub
Private Sub LiteLoader_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnLiteLoaderClear.MouseLeftButtonUp
Private Sub LiteLoader_Clear(sender As Object, e As EventArgs) Handles BtnLiteLoaderClear.Click
SelectedLiteLoader = Nothing
CardLiteLoader.IsSwapped = True
e.Handled = True
ReloadSelected()
End Sub
@@ -713,8 +726,8 @@ Public Class PageDownloadInstall
''' <summary>
''' 获取 Forge 的加载异常信息。若正常则返回 Nothing。
''' </summary>
Private Function LoadForgeGetError() As String
If CompareVersionGE("1.5.1", VanillaName) AndAlso CompareVersionGE(VanillaName, "1.1") Then Return "无"
Public Function LoadForgeGetError() As String
If McUtils.CompareVersionGE("1.5.1", VanillaName) AndAlso McUtils.CompareVersionGE(VanillaName, "1.1") Then Return "无"
'检查 Loader
If GetLoaderError(LoadForge) IsNot Nothing Then Return GetLoaderError(LoadForge)
Dim Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = LoadForge.State
@@ -725,7 +738,7 @@ Public Class PageDownloadInstall
If SelectedNeoForge IsNot Nothing Then Return "与 NeoForge 不兼容"
If SelectedFabric IsNot Nothing Then Return "与 Fabric 不兼容"
If SelectedOptiFine IsNot Nothing AndAlso
CompareVersionGE(VanillaName, "1.13") AndAlso CompareVersionGE("1.14.3", VanillaName) Then
McUtils.CompareVersionGE(VanillaName, "1.13") AndAlso McUtils.CompareVersionGE("1.14.3", VanillaName) Then
Return "与 OptiFine 不兼容" '1.13 ~ 1.14.3 OptiFine 检查
End If
If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, Version) Then Continue For
@@ -775,10 +788,9 @@ Public Class PageDownloadInstall
OptiFine_Loaded()
ReloadSelected()
End Sub
Private Sub Forge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnForgeClear.MouseLeftButtonUp
Private Sub Forge_Clear(sender As Object, e As EventArgs) Handles BtnForgeClear.Click
SelectedForge = Nothing
CardForge.IsSwapped = True
e.Handled = True
OptiFine_Loaded()
ReloadSelected()
End Sub
@@ -790,7 +802,7 @@ Public Class PageDownloadInstall
''' <summary>
''' 获取 NeoForge 的加载异常信息。若正常则返回 Nothing。
''' </summary>
Private Function LoadNeoForgeGetError() As String
Public Function LoadNeoForgeGetError() As String
If SelectedOptiFine IsNot Nothing Then Return "与 OptiFine 不兼容"
If SelectedForge IsNot Nothing Then Return "与 Forge 不兼容"
If SelectedFabric IsNot Nothing Then Return "与 Fabric 不兼容"
@@ -832,10 +844,9 @@ Public Class PageDownloadInstall
OptiFine_Loaded()
ReloadSelected()
End Sub
Private Sub NeoForge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnNeoForgeClear.MouseLeftButtonUp
Private Sub NeoForge_Clear(sender As Object, e As EventArgs) Handles BtnNeoForgeClear.Click
SelectedNeoForge = Nothing
CardNeoForge.IsSwapped = True
e.Handled = True
OptiFine_Loaded()
ReloadSelected()
End Sub
@@ -847,9 +858,9 @@ Public Class PageDownloadInstall
''' <summary>
''' 获取 Fabric 的加载异常信息。若正常则返回 Nothing。
''' </summary>
Private Function LoadFabricGetError() As String
Public Function LoadFabricGetError() As String
'检查 OptiFine 1.20.5+:没有 OptiFabric 故全部不兼容
If SelectedOptiFine IsNot Nothing AndAlso CompareVersionGE(VanillaName, "1.20.5") Then Return "与 OptiFine 不兼容"
If SelectedOptiFine IsNot Nothing AndAlso McUtils.CompareVersionGE(VanillaName, "1.20.5") Then Return "与 OptiFine 不兼容"
'检查 Loader
If GetLoaderError(LoadFabric) IsNot Nothing Then Return GetLoaderError(LoadFabric)
'检查版本
@@ -879,9 +890,10 @@ Public Class PageDownloadInstall
If Not Versions.Any() Then Return
'可视化
PanFabric.Children.Clear()
PanFabric.Tag = Versions
CardFabric.SwapControl = PanFabric
CardFabric.SwapType = 12
CardFabric.SetContentBuilder(Versions.OfType(Of JObject),
Function(Version) FabricDownloadListItem(Version, AddressOf Fabric_Selected))
If Not CardFabric.IsSwapped Then CardFabric.BuildContent()
Catch ex As Exception
Logger.Error(ex, "可视化 Fabric 安装版本列表出错")
End Try
@@ -895,14 +907,15 @@ Public Class PageDownloadInstall
CardFabric.IsSwapped = True
ReloadSelected()
End Sub
Private Sub Fabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricClear.MouseLeftButtonUp
Private Sub Fabric_Clear(sender As Object, e As EventArgs) Handles BtnFabricClear.Click
SelectedFabric = Nothing
SelectedFabricApi = Nothing
BtnFabricApiClear.Visibility = Visibility.Collapsed
AutoSelectedFabricApi = False
SelectedOptiFabric = Nothing
BtnOptiFabricClear.Visibility = Visibility.Collapsed
AutoSelectedOptiFabric = False
CardFabric.IsSwapped = True
e.Handled = True
ReloadSelected()
End Sub
@@ -911,14 +924,14 @@ Public Class PageDownloadInstall
#Region "Fabric API 列表"
''' <summary>
''' 判断某 Fabric API 是否适配当前选择的原版版本。
''' 判断某 Fabric API 是否适配指定的原版版本。
''' </summary>
Public Function IsFabricApiCompatible(FabricApi As ResourceVersion) As Boolean
Public Shared Function IsFabricApiCompatible(FabricApi As ResourceVersion, MinecraftVersion As String) As Boolean
Dim FabricApiName = FabricApi.Display
Try
If FabricApiName Is Nothing OrElse VanillaName Is Nothing Then Return False
If FabricApiName Is Nothing OrElse MinecraftVersion Is Nothing Then Return False
FabricApiName = FabricApiName.Lower
Dim TargetName = VanillaName.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3").Lower
Dim TargetName = MinecraftVersion.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3").Lower
If FabricApiName.StartsWithF("[" & TargetName & "]") Then Return True
If Not FabricApiName.Contains("/") OrElse Not FabricApiName.Contains("]") Then Return False
'直接的判断(例如 1.18.1/22w03a)
@@ -946,7 +959,7 @@ Public Class PageDownloadInstall
End While
Return True
Catch ex As Exception
Logger.Warn(ex, $"判断 Fabric API 版本适配性出错({FabricApiName}, {VanillaName})")
Logger.Warn(ex, $"判断 Fabric API 版本适配性出错({FabricApiName}, {MinecraftVersion})")
Return False
End Try
End Function
@@ -954,12 +967,12 @@ Public Class PageDownloadInstall
''' <summary>
''' 获取 FabricApi 的加载异常信息。若正常则返回 Nothing。
''' </summary>
Private Function LoadFabricApiGetError() As String
Public Function LoadFabricApiGetError() As String
'检查 Loader
If GetLoaderError(LoadFabricApi) IsNot Nothing Then Return GetLoaderError(LoadFabricApi)
If DlFabricApiLoader.Output Is Nothing Then Return If(SelectedFabric Is Nothing, "需要安装 Fabric", "获取中……")
'检查版本
If DlFabricApiLoader.Output.Any(Function(f) IsFabricApiCompatible(f)) Then
If DlFabricApiLoader.Output.Any(Function(f) IsFabricApiCompatible(f, VanillaName)) Then
Return If(SelectedFabric Is Nothing, "需要安装 Fabric", Nothing)
Else
Return "无"
@@ -971,7 +984,7 @@ Public Class PageDownloadInstall
If LoadFabricApiGetError() IsNot Nothing Then e.Handled = True
End Sub
Private AutoSelectedFabricApi As Boolean = False
Public AutoSelectedFabricApi As Boolean = False
''' <summary>
''' 尝试重新可视化 FabricApi 版本列表。
''' </summary>
@@ -982,7 +995,7 @@ Public Class PageDownloadInstall
'获取版本列表
Dim Versions As New List(Of ResourceVersion)
For Each Version In DlFabricApiLoader.Output
If IsFabricApiCompatible(Version) Then
If IsFabricApiCompatible(Version, VanillaName) Then
If Not Version.Display.StartsWithF("[") Then
Logger.Warn($"已特判修改 Fabric API 显示名:{Version.Display}")
Version.Display = "[" & VanillaName & "] " & Version.Display
@@ -995,7 +1008,6 @@ Public Class PageDownloadInstall
'可视化
PanFabricApi.Children.Clear()
For Each Version In Versions
If Not IsFabricApiCompatible(Version) Then Continue For
PanFabricApi.Children.Add(FabricApiDownloadListItem(Version, AddressOf FabricApi_Selected))
Next
'自动选择 Fabric API
@@ -1016,10 +1028,9 @@ Public Class PageDownloadInstall
CardFabricApi.IsSwapped = True
ReloadSelected()
End Sub
Private Sub FabricApi_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricApiClear.MouseLeftButtonUp
Private Sub FabricApi_Clear(sender As Object, e As EventArgs) Handles BtnFabricApiClear.Click
SelectedFabricApi = Nothing
CardFabricApi.IsSwapped = True
e.Handled = True
ReloadSelected()
End Sub
@@ -1028,23 +1039,23 @@ Public Class PageDownloadInstall
#Region "OptiFabric 列表"
''' <summary>
''' 判断某 OptiFabric 是否适配当前选择的原版版本。
''' 判断某 OptiFabric 是否适配指定的原版版本。
''' </summary>
Private Function IsOptiFabricCompatible(ModFile As ResourceVersion) As Boolean
Public Shared Function IsOptiFabricCompatible(ModFile As ResourceVersion, MinecraftVersion As String) As Boolean
Try
If VanillaName Is Nothing Then Return False
Return ModFile.GameVersions.Contains(VanillaName)
If MinecraftVersion Is Nothing Then Return False
Return ModFile.GameVersions.Contains(MinecraftVersion)
Catch ex As Exception
Logger.Warn(ex, $"判断 OptiFabric 版本适配性出错({VanillaName})")
Logger.Warn(ex, $"判断 OptiFabric 版本适配性出错({MinecraftVersion})")
Return False
End Try
End Function
Private AutoSelectedOptiFabric As Boolean = False
Public AutoSelectedOptiFabric As Boolean = False
''' <summary>
''' 获取 OptiFabric 的加载异常信息。若正常则返回 Nothing。
''' </summary>
Private Function LoadOptiFabricGetError() As String
Public Function LoadOptiFabricGetError() As String
If VanillaDrop >= 140 AndAlso VanillaDrop <= 150 Then Return "不兼容老版本 Fabric,请手动下载 OptiFabric Origins"
'检查 Loader
If GetLoaderError(LoadOptiFabric) IsNot Nothing Then Return GetLoaderError(LoadOptiFabric)
@@ -1056,7 +1067,7 @@ Public Class PageDownloadInstall
Return "获取中……"
End If
For Each Version In DlOptiFabricLoader.Output
If Not IsOptiFabricCompatible(Version) Then Continue For '2135#
If Not IsOptiFabricCompatible(Version, VanillaName) Then Continue For '2135#
If SelectedFabric Is Nothing AndAlso SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine 与 Fabric"
If SelectedFabric Is Nothing Then Return "需要安装 Fabric"
If SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine"
@@ -1080,7 +1091,7 @@ Public Class PageDownloadInstall
'获取版本列表
Dim Versions As New List(Of ResourceVersion)
For Each Version In DlOptiFabricLoader.Output
If IsOptiFabricCompatible(Version) Then Versions.Add(Version)
If IsOptiFabricCompatible(Version, VanillaName) Then Versions.Add(Version)
Next
If Not Versions.Any() Then Return
'排序
@@ -1088,7 +1099,6 @@ Public Class PageDownloadInstall
'可视化
PanOptiFabric.Children.Clear()
For Each Version In Versions
If Not IsOptiFabricCompatible(Version) Then Continue For
PanOptiFabric.Children.Add(OptiFabricDownloadListItem(Version, AddressOf OptiFabric_Selected))
Next
'自动选择 OptiFabric
@@ -1108,10 +1118,9 @@ Public Class PageDownloadInstall
CardOptiFabric.IsSwapped = True
ReloadSelected()
End Sub
Private Sub OptiFabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFabricClear.MouseLeftButtonUp
Private Sub OptiFabric_Clear(sender As Object, e As EventArgs) Handles BtnOptiFabricClear.Click
SelectedOptiFabric = Nothing
CardOptiFabric.IsSwapped = True
e.Handled = True
ReloadSelected()
End Sub
@@ -1119,10 +1128,42 @@ Public Class PageDownloadInstall
#Region "安装"
''' <summary>
''' 获取当前选择的组件信息。
''' </summary>
Public Function GetSelectedComponents() As McComponents
Return New McComponents(
minecraftVersion:=VanillaName,
optiFineEntry:=If(SelectedOptiFine Is Nothing, Nothing, JObject.FromObject(SelectedOptiFine)),
forgeVersion:=SelectedForge?.VersionName,
neoForgeVersion:=SelectedNeoForge?.VersionName,
fabricVersion:=SelectedFabric,
fabricApiVersion:=If(SelectedFabricApi?.Version, If(SelectedFabricApi?.Id, SelectedFabricApi?.Display)),
optiFabricVersion:=If(SelectedOptiFabric?.Version, If(SelectedOptiFabric?.Id, SelectedOptiFabric?.Display)),
liteLoaderVersion:=SelectedLiteLoader?.JsonToken?("version").ToString())
End Function
Private Sub TextSelectName_KeyDown(sender As Object, e As KeyEventArgs) Handles TextSelectName.KeyDown
If e.Key = Key.Enter AndAlso BtnStart.IsEnabled Then BtnStart_Click()
If e.Key = Key.Enter AndAlso IsInSelectPage AndAlso BtnStart.IsEnabled Then BtnStart_Click()
End Sub
Private Sub BtnStart_Click() Handles BtnStart.Click
'提交安装申请
Dim InstanceName As String = TextSelectName.Text
Dim Request As New McInstallRequest With {
.NewInstanceName = InstanceName,
.InstanceFolder = $"{McFolderSelected}versions\{InstanceName}\",
.Components = GetSelectedComponents(),
.MinecraftJson = VanillaData("url").ToString,
.ForgeEntry = SelectedForge,
.NeoForgeEntry = SelectedNeoForge,
.FabricApi = SelectedFabricApi,
.OptiFabric = SelectedOptiFabric,
.LiteLoaderEntry = SelectedLiteLoader
}
StartInstall(Request)
End Sub
Public Overridable Sub StartInstall(Request As McInstallRequest)
'确认版本隔离
If (SelectedForge IsNot Nothing OrElse SelectedNeoForge IsNot Nothing OrElse SelectedFabric IsNot Nothing) AndAlso
(Settings.Get(Of Integer)("LaunchArgumentIndieV2") = 0 OrElse Settings.Get(Of Integer)("LaunchArgumentIndieV2") = 2) Then
@@ -1132,22 +1173,7 @@ Public Class PageDownloadInstall
Return
End If
End If
'提交安装申请
Dim InstanceName As String = TextSelectName.Text
Dim Request As New McInstallRequest With {
.NewInstanceName = InstanceName,
.VersionFolder = $"{McFolderSelected}versions\{InstanceName}\",
.MinecraftJson = VanillaData("url").ToString,
.MinecraftName = VanillaName,
.OptiFineEntry = SelectedOptiFine,
.ForgeEntry = SelectedForge,
.NeoForgeEntry = SelectedNeoForge,
.FabricVersion = SelectedFabric,
.FabricApi = SelectedFabricApi,
.OptiFabric = SelectedOptiFabric,
.LiteLoaderEntry = SelectedLiteLoader
}
If Not McInstall(Request) Then Return
If Not McInstallStart(Request) Then Return
'返回,这样在再次进入安装页面时这个版本就会显示文件夹已重复
ExitSelectPage()
End Sub
@@ -0,0 +1,727 @@
''' <summary>
''' 版本修改页:完整还原已安装组件后,复用安装页的选择交互。
''' </summary>
Public Class PageDownloadModify
Inherits PageDownloadInstall
Private InitLoader As LoaderTask(Of McInstance, Object)
Private ForgeVersionLoader As LoaderTask(Of String, List(Of DlForgeVersionEntry))
Private InitialTarget As McComponents '进入时的组件快照
''' <summary>
''' 原版的版本号是否可靠(不是通过 json 模糊分析)。
''' </summary>
Private IsVanillaVersionReliable As Boolean
Private OriginalName As String
Private CustomLogo As String
''' <summary>
''' 开始修改 MC 版本时,记录当前选择的组件,以在修改 MC 版本后尝试自动选中相同组件。
''' </summary>
Private ReadOnly PendingComponents As New Dictionary(Of MyLoading, String)
Private IsRestoringComponents As Boolean
Public Overrides Sub LoaderInit() '父级 Handles Me.Initialized
ControlInit()
InitLoader = New LoaderTask(Of McInstance, Object)("初始化版本修改", AddressOf LoadInstance)
PageLoaderInit(LoadMinecraft, PanLoad, PanAllBack, Nothing, InitLoader, AddressOf LoadInstanceFinished, Function() PageInstanceLeft.Instance)
End Sub
Public Overrides Sub Init() '父级 Handles Me.Loaded
PanBack.ScrollToHome()
Static IsLoad As Boolean = False
If IsLoad Then PageLoaderRestart()
IsLoad = True
End Sub
Private Sub PageUnloaded() Handles Me.Unloaded
InitLoader.Cancel()
End Sub
''' <summary>
''' 读取或初始化版本的安装清单。
''' </summary>
Private Sub LoadInstance(Task As LoaderTask(Of McInstance, Object))
'旧状态清理
RunInUiWait(
Sub()
AniStop("FrmDownloadInstall SelectPageSwitch")
IsSelectNameEdited = False
ClearSelected()
PendingComponents.Clear()
InitialTarget = Nothing
IsInSelectPage = False
PanAllBack.IsHitTestVisible = False
End Sub)
Task.Input.Config.DiscardCache()
'获取 Components
Dim Manifest = Configs.McInstallManifest.Get(Task.Input.Config)
If Manifest IsNot Nothing AndAlso Manifest.SchemaVersion > Versions.McInstallManifestVersion Then
Logger.Warn($"安装清单由更新版本的 PCL 创建(清单版本:{Manifest.SchemaVersion},当前版本:{Versions.McInstallManifestVersion})")
Throw New Exception($"更新 PCL 后才能修改它!点击【设置 → 其他 → 检查更新】以更新。")
End If
If Manifest IsNot Nothing Then
Dim LatestManifest = Manifest.RefreshModComponents(Task.Input.PathIndie)
If LatestManifest IsNot Manifest Then
Manifest = LatestManifest
Configs.McInstallManifest.Set(Manifest, Task.Input.Config)
Task.Input.Config.Save()
End If
End If
Dim Components As McComponents
Dim OptiFineVersion As String = Nothing
If Manifest IsNot Nothing Then
'有清单:从清单读取
Components = Manifest.Components
If Components Is Nothing Then Throw New Exception("安装清单缺少组件信息,无法还原已安装组件。")
IsVanillaVersionReliable = True
Else
'无清单:通过版本 JSON 获取
Dim VersionInfo = New McInstance(Task.Input.PathVersion).Version
If VersionInfo.VanillaName = "Unknown" Then Throw New Exception("无法确定 Minecraft 版本,无法修改")
Dim UnknownLoaders = VersionInfo.ModLoaderVersions.
Where(Function(Loader) String.IsNullOrWhiteSpace(Loader.Value) OrElse Loader.Value = "未知版本").
Select(Function(Loader) Loader.Key.ToString()).ToList()
If UnknownLoaders.Any() Then Throw New Exception($"无法确定 {UnknownLoaders.Join("、")} 的版本,无法修改")
Components = New McComponents(VersionInfo.VanillaName, VersionInfo.ModLoaderVersions)
OptiFineVersion = VersionInfo.OptiFine
IsVanillaVersionReliable = VersionInfo.Reliable
End If
Dim MinecraftVersion = Components.MinecraftVersion
If String.IsNullOrWhiteSpace(MinecraftVersion) Then Throw New Exception("无法确定当前 Minecraft 版本,请检查版本文件")
'等待子列表加载器完成获取
Dim LoaderList As New Dictionary(Of String, LoaderBase) From {
{"Minecraft", DlClientListLoader}, {"OptiFine", DlOptiFineListLoader}, {"LiteLoader", DlLiteLoaderListLoader},
{"NeoForge", DlNeoForgeListLoader}, {"Fabric", DlFabricListLoader}, {"Fabric API", DlFabricApiLoader}, {"OptiFabric", DlOptiFabricLoader}}
Dim ForgeLoader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = Nothing
If McVersion.IsStandardFormat(MinecraftVersion) Then
ForgeLoader = New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion " & MinecraftVersion, AddressOf DlForgeVersionMain)
LoaderList.Add("Forge", ForgeLoader)
End If
For Each Loader In LoaderList.Values
Loader.Start(If(Loader Is ForgeLoader, MinecraftVersion, Nothing), Task.IsForceRestarting)
Next
Do
If Task.IsCanceled Then Return
For Each Pair In LoaderList
Dim Loader = Pair.Value
If Loader.State = LoadState.Failed Then
If Loader Is ForgeLoader AndAlso Loader.Error.Message = "无" Then Continue For 'Forge 无版本时不报错
Throw New Exception($"获取 {Pair.Key} 版本列表失败,请稍后再试,或使用 VPN 改善网络环境", Loader.Error)
ElseIf Loader.State = LoadState.Canceled Then
Throw New Exception($"获取 {Pair.Key} 版本列表被中断,请稍后再试,或使用 VPN 改善网络环境")
End If
Next
If LoaderList.Values.All(Function(Loader) Loader.State = LoadState.Finished OrElse Loader.State = LoadState.Failed) Then Exit Do
Task.Progress = 0.7 * LoaderList.Values.Average(Function(Loader) Loader.Progress)
Thread.Sleep(10)
Loop
If Task.IsCanceled Then Return
Task.Progress = 0.7
'Minecraft
MinecraftVersions = CType(DlClientListLoader.Output.Value("versions").DeepClone(), JArray)
VanillaData = MinecraftVersions.OfType(Of JObject).FirstOrDefault(
Function(v) v("id").ToString.Replace("∞", "infinite") = MinecraftVersion.Replace("∞", "infinite"))
If VanillaData Is Nothing Then Throw New Exception($"Minecraft 列表中没有已安装的 {MinecraftVersion}")
VanillaData("id") = MinecraftVersion
'OptiFine
If Manifest IsNot Nothing Then
SelectedOptiFine = Components.OptiFineEntry?.ToObject(Of DlOptiFineListEntry)()
If SelectedOptiFine IsNot Nothing AndAlso Not DlOptiFineListLoader.Output.Value.Any(Function(v) v.FileName = SelectedOptiFine.FileName) Then
'如果 OptiFine 是 pre 版,可能只在 BMCLAPI 下有,官方源删了。如果是这种缺失数据的情况,换用 BMCLAPI 源作为输出
If DlOptiFineListBmclapiLoader.State <> LoadState.Finished Then DlOptiFineListBmclapiLoader.WaitForExit()
DlOptiFineListLoader.Output = DlOptiFineListBmclapiLoader.Output
End If
ElseIf OptiFineVersion IsNot Nothing AndAlso OptiFineVersion <> "未知版本" Then
SelectedOptiFine = DlOptiFineListLoader.Output.Value.FirstOrDefault(
Function(v) v.Inherit = MinecraftVersion AndAlso
v.InstanceName.AfterLast("-OptiFine_").Replace("HD_U_", "") = OptiFineVersion.Replace("HD_U_", ""))
If SelectedOptiFine Is Nothing Then Throw New Exception($"OptiFine 列表中没有已安装的 {OptiFineVersion}")
End If
'Forge
If Components.ModLoaderVersions.ContainsKey(ModLoaders.Forge) Then
SelectedForge = ForgeLoader?.Output?.FirstOrDefault(
Function(v) v.Inherit = MinecraftVersion AndAlso v.VersionName = Components.ModLoaderVersions(ModLoaders.Forge) AndAlso
v.Category <> "universal" AndAlso v.Category <> "client" AndAlso (SelectedOptiFine Is Nothing OrElse IsOptiFineSuitForForge(SelectedOptiFine, v)))
If SelectedForge Is Nothing Then Throw New Exception($"Forge 列表中没有已安装的 {Components.ModLoaderVersions(ModLoaders.Forge)}")
End If
'NeoForge
If Components.ModLoaderVersions.ContainsKey(ModLoaders.NeoForge) Then
SelectedNeoForge = DlNeoForgeListLoader.Output.Value.FirstOrDefault(
Function(v) v.Inherit = MinecraftVersion AndAlso v.VersionName = Components.ModLoaderVersions(ModLoaders.NeoForge))
If SelectedNeoForge Is Nothing Then Throw New Exception($"NeoForge 列表中没有已安装的 {Components.ModLoaderVersions(ModLoaders.NeoForge)}")
End If
'Fabric
If Components.ModLoaderVersions.ContainsKey(ModLoaders.Fabric) Then
Dim FabricEntry = DlFabricListLoader.Output.Value("loader").OfType(Of JObject).FirstOrDefault(
Function(v) v("version").ToString = Components.ModLoaderVersions(ModLoaders.Fabric),
Function(v) v("version").ToString.Replace("+build", "") = Components.ModLoaderVersions(ModLoaders.Fabric))
If FabricEntry Is Nothing OrElse Not DlFabricListLoader.Output.Value("game").OfType(Of JObject).
Any(Function(v) v("version").ToString = MinecraftVersion.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3")) Then
Throw New Exception($"Fabric 列表中没有已安装的 {Components.ModLoaderVersions(ModLoaders.Fabric)}")
End If
SelectedFabric = FabricEntry("version").ToString
End If
'LiteLoader
If Components.ModLoaderVersions.ContainsKey(ModLoaders.LiteLoader) Then
SelectedLiteLoader = DlLiteLoaderListLoader.Output.Value.FirstOrDefault(
Function(v) v.Inherit = MinecraftVersion AndAlso v.JsonToken("version").ToString = Components.ModLoaderVersions(ModLoaders.LiteLoader))
If SelectedLiteLoader Is Nothing Then Throw New Exception($"LiteLoader 列表中没有已安装的 {Components.ModLoaderVersions(ModLoaders.LiteLoader)}")
End If
'Fabric API
If Components.FabricApiVersion IsNot Nothing Then
SelectedFabricApi = DlFabricApiLoader.Output.FirstOrDefault(
Function(v) (v.Version = Components.FabricApiVersion OrElse v.Id = Components.FabricApiVersion OrElse
v.Display = Components.FabricApiVersion) AndAlso IsFabricApiCompatible(v, MinecraftVersion))
If SelectedFabricApi Is Nothing Then Throw New Exception($"Fabric API 列表中没有已安装的 {Components.FabricApiVersion}")
End If
'OptiFabric
If Components.OptiFabricVersion IsNot Nothing Then
SelectedOptiFabric = DlOptiFabricLoader.Output.FirstOrDefault(
Function(v) (v.Version = Components.OptiFabricVersion OrElse v.Id = Components.OptiFabricVersion OrElse
v.Display = Components.OptiFabricVersion) AndAlso IsOptiFabricCompatible(v, MinecraftVersion))
If SelectedOptiFabric Is Nothing Then Throw New Exception($"OptiFabric 列表中没有已安装的 {Components.OptiFabricVersion}")
End If
'扫描尚未识别的 Mod 组件
Dim ManagedFiles As New List(Of McInstallManifest.ManagedFile)
If Manifest IsNot Nothing Then ManagedFiles.AddRange(Manifest.ManagedFiles)
#Region "从 mods 文件夹中获取以 Mod 形式安装的组件信息"
'获取 mods 文件夹下的 jar 文件与 SHA1
Dim Mods As New Lazy(Of List(Of (File As FileInfo, Sha1 As String)))(
Function()
Dim ModsFolder = Task.Input.PathIndie & "mods\"
Dim Files = DirectoryUtils.EnumerateFiles(ModsFolder, searchPattern:="*.jar").ToList()
If SelectedLiteLoader IsNot Nothing Then 'LiteLoader 的 OptiFine 安装目录
Files.AddRange(DirectoryUtils.EnumerateFiles(Path.Combine(ModsFolder, MinecraftVersion), searchPattern:="*.jar"))
End If
Dim Entries As New List(Of (File As FileInfo, Sha1 As String))
For Each FilePath In Files
If Task.IsCanceled Then Return Entries
Dim File = FileUtils.GetInfo(FilePath)
Entries.Add((File, New LocalResourceFile(File).ModrinthHash))
Task.Progress = 0.7 + 0.28 * Entries.Count / Files.Count
Next
Return Entries
End Function)
'Fabric API
If SelectedFabric IsNot Nothing AndAlso SelectedFabricApi Is Nothing Then
Dim FabricApiList = DlFabricApiLoader.Output.Where(Function(v) IsFabricApiCompatible(v, MinecraftVersion)).ToList()
Dim Matches As New List(Of (File As FileInfo, Sha1 As String, Version As ResourceVersion))
For Each Entry In Mods.Value
Dim Match = FabricApiList.FirstOrDefault(Function(v) v.Hash = Entry.Sha1)
If Match Is Nothing Then Continue For
Matches.Add((Entry.File, Entry.Sha1, Match))
Next
Dim SelectedFile = SelectMod("Fabric API", Matches)
If SelectedFile IsNot Nothing Then
SelectedFabricApi = SelectedFile.Value.Version
ManagedFiles.Add(New McInstallManifest.ManagedFile("mods/" & SelectedFile.Value.File.Name.Lower(), SelectedFile.Value.Sha1, SelectedFile.Value.File.LastWriteTimeUtc, "FabricApi"))
End If
End If
If Task.IsCanceled Then Return
'OptiFabric
If SelectedFabric IsNot Nothing AndAlso SelectedOptiFabric Is Nothing Then
Dim OptiFabricList = DlOptiFabricLoader.Output.Where(Function(v) IsOptiFabricCompatible(v, MinecraftVersion)).ToList()
Dim HasMd5 = OptiFabricList.Any(Function(v) If(v.Hash, "").Length = 32) 'CurseForge 的 OptiFabric 信息可能提供 SHA1 或 MD5
Dim Matches As New List(Of (File As FileInfo, Sha1 As String, Version As ResourceVersion))
For Each Entry In Mods.Value
Dim Match = OptiFabricList.FirstOrDefault(Function(v) v.Hash = Entry.Sha1)
If Match Is Nothing AndAlso HasMd5 Then
Dim Md5 = CryptographyUtils.ComputeFileHash(Entry.File.FullName, CryptographyUtils.HashMethod.Md5)
Match = OptiFabricList.FirstOrDefault(Function(v) v.Hash = Md5)
End If
If Match Is Nothing Then Continue For
Matches.Add((Entry.File, Entry.Sha1, Match))
Next
Dim SelectedFile = SelectMod("OptiFabric", Matches)
If SelectedFile IsNot Nothing Then
SelectedOptiFabric = SelectedFile.Value.Version
ManagedFiles.Add(New McInstallManifest.ManagedFile("mods/" & SelectedFile.Value.File.Name.Lower(), SelectedFile.Value.Sha1, SelectedFile.Value.File.LastWriteTimeUtc, "OptiFabric"))
End If
End If
If Task.IsCanceled Then Return
'OptiFine
Dim OptiFineFiles As New List(Of (File As FileInfo, Sha1 As String, OriginalName As String))
If SelectedOptiFine Is Nothing AndAlso (SelectedForge IsNot Nothing OrElse SelectedLiteLoader IsNot Nothing OrElse SelectedFabric IsNot Nothing) Then 'OptiFine 只在有 Mod 加载器时才看在不在 mods 文件夹中
Static OptiFineHashes As JObject = FileUtils.ReadAsJson("Minecraft\OptiFineHashes.json", type:=GetType(Resource)) 'JSON 内容:{"SHA1": "原始文件名", ...}
OptiFineFiles = Mods.Value.
Select(Function(Entry) (Entry.File, Entry.Sha1, OriginalName:=OptiFineHashes(Entry.Sha1)?.ToString())). '用本地文件的 SHA1 匹配下载时的原始文件名,再根据原始文件名取得下载源信息
Where(Function(Entry) Entry.OriginalName IsNot Nothing).ToList()
Dim Matches As New List(Of (File As FileInfo, Sha1 As String, Version As DlOptiFineListEntry))
For Each Entry In OptiFineFiles '遍历 hash 匹配到了的本地文件
Dim Match = DlOptiFineListLoader.Output.Value.FirstOrDefault(
Function(v) v.Inherit = MinecraftVersion AndAlso v.FileName = Entry.OriginalName AndAlso (SelectedForge Is Nothing OrElse IsOptiFineSuitForForge(v, SelectedForge)))
If Match Is Nothing Then
Hint($"无法从 OptiFine 列表还原 {Entry.File.Name} 对应的 {Entry.OriginalName},将跳过该文件。")
Continue For
End If
Matches.Add((Entry.File, Entry.Sha1, Match))
Next
Dim SelectedFile = SelectMod("OptiFine", Matches)
If SelectedFile IsNot Nothing Then
SelectedOptiFine = SelectedFile.Value.Version
Dim RelativePath = PathUtils.RemoveExtendedPrefix(SelectedFile.Value.File.FullName).Substring(Task.Input.PathIndie.Length).Replace("\", "/").Lower()
ManagedFiles.Add(New McInstallManifest.ManagedFile(RelativePath, SelectedFile.Value.Sha1, SelectedFile.Value.File.LastWriteTimeUtc, "OptiFine"))
End If
End If
If Task.IsCanceled Then Return
#End Region
'保存 Manifest
If IsVanillaVersionReliable Then
Components = New McComponents(MinecraftVersion, Components.ModLoaderVersions,
If(SelectedOptiFine Is Nothing, Nothing, JObject.FromObject(SelectedOptiFine)),
If(SelectedFabricApi?.Version, If(SelectedFabricApi?.Id, SelectedFabricApi?.Display)),
If(SelectedOptiFabric?.Version, If(SelectedOptiFabric?.Id, SelectedOptiFabric?.Display)))
If Manifest Is Nothing OrElse Not Components.IsEquivalentTo(Manifest.Components) OrElse Not ManagedFiles.SequenceEqual(Manifest.ManagedFiles) Then
Configs.McInstallManifest.Set(New McInstallManifest(Versions.McInstallManifestVersion, Components, Date.UtcNow, ManagedFiles), Task.Input.Config)
Task.Input.Config.Save()
Logger.Info($"已为 {Task.Input.Name} 更新安装清单")
End If
End If
If Task.IsCanceled Then Return
ForgeVersionLoader = ForgeLoader
End Sub
Private Function SelectMod(Of T)(ComponentName As String, Matches As List(Of (File As FileInfo, Sha1 As String, Version As T))) As (File As FileInfo, Sha1 As String, Version As T)?
If Matches.Count = 0 Then Return Nothing
If Matches.Count = 1 Then Return Matches(0)
Dim Choice As Integer? = Nothing
RunInUiWait(Sub() Choice = MyMsgBoxSelect(Matches.Select(Function(Entry) CType(New MyRadioBox With {.Text = Entry.File.Name}, IMyRadio)), $"选择当前使用的 {ComponentName}"))
If Not Choice.HasValue Then Throw New Exception($"发现了多个 {ComponentName}")
Return Matches(Choice.Value)
End Function
''' <summary>
''' 初始化修改页
''' </summary>
Private Sub LoadInstanceFinished(Task As LoaderTask(Of McInstance, Object))
'还原原版信息
OriginalName = Task.Input.Name
CustomLogo = Nothing
If ReadIni(Task.Input.PathVersion & "PCL\Setup.ini", "LogoCustom", False) Then
CustomLogo = If(Task.Input.Logo.EndsWithF("PCL\Logo.png"), Task.Input.PathVersion & "PCL\Logo.png", Task.Input.Logo)
End If
Dim Item = McDownloadListItem(VanillaData, Nothing, False).Init()
VanillaName = Item.Title
VanillaIcon = Item.Logo
LabMinecraftVersion.Text = VanillaName
TextSelectName.Text = OriginalName
IsSelectNameEdited = Not String.Equals(OriginalName, GetSelectName(), StringComparison.Ordinal)
TextSelectName.ValidateRules = New ObjectModel.Collection(Of Validate) From {
New ValidateFolderName(McFolderSelected & "versions", IgnoreList:=New List(Of String) From {OriginalName})}
'修改页 UI
CardMinecraft.Visibility = If(RestrictMcDownload, Visibility.Collapsed, Visibility.Visible)
BtnBack.Visibility = Visibility.Collapsed
ImgLogo.Margin = New Thickness(15, 15, 0, 0)
TextSelectName.Margin = New Thickness(56, 16, 16, 0)
LabSelectName.Text = $"原名 :   {OriginalName}"
UpdateOriginalNameVisibility()
BtnStart.Text = "开始修改"
BtnStart.Logo = FrmInstanceLeft.ItemModify.Logo
AutoSelectedFabricApi = SelectedFabric IsNot Nothing
AutoSelectedOptiFabric = SelectedFabric IsNot Nothing AndAlso SelectedOptiFine IsNot Nothing
If ForgeVersionLoader IsNot Nothing Then LoadForge.State = ForgeVersionLoader
PrepareSelectPage()
ReloadComponentLists()
'进入动画
PanMinecraft.Visibility = Visibility.Collapsed
PanMinecraft.Opacity = 0
PanMinecraft.RenderTransform = New TranslateTransform(-50, 0)
PanSelect.Opacity = 1
PanSelect.RenderTransform = New TranslateTransform
PanBack.IsHitTestVisible = True
PanAllBack.IsHitTestVisible = True
InitialTarget = GetSelectedComponents()
'设置原始数据
BtnMinecraftRestore.Tag = VanillaData
BtnOptiFineRestore.Tag = SelectedOptiFine
BtnForgeRestore.Tag = SelectedForge
BtnNeoForgeRestore.Tag = SelectedNeoForge
BtnFabricRestore.Tag = SelectedFabric
BtnFabricApiRestore.Tag = SelectedFabricApi
BtnOptiFabricRestore.Tag = SelectedOptiFabric
BtnLiteLoaderRestore.Tag = SelectedLiteLoader
ImgMinecraftOld.Source = VanillaIcon
LabMinecraftOld.Text = LabMinecraftVersion.Text
LabOptiFineOld.Text = If(SelectedOptiFine Is Nothing, "无", LabOptiFine.Text)
LabForgeOld.Text = If(SelectedForge Is Nothing, "无", LabForge.Text)
LabNeoForgeOld.Text = If(SelectedNeoForge Is Nothing, "无", LabNeoForge.Text)
LabFabricOld.Text = If(SelectedFabric Is Nothing, "无", LabFabric.Text)
LabLiteLoaderOld.Text = If(SelectedLiteLoader Is Nothing, "无", LabLiteLoader.Text)
LabFabricApiOld.Text = If(SelectedFabricApi Is Nothing, "无", LabFabricApi.Text)
LabOptiFabricOld.Text = If(SelectedOptiFabric Is Nothing, "无", LabOptiFabric.Text)
RefreshComparisons()
End Sub
Public Overrides Function GetSelectLogo() As String
Return If(CustomLogo, MyBase.GetSelectLogo())
End Function
Public Overrides Sub ShowMinecraftVersionList()
If IsInSelectPage Then
'把当前已选择的组件记录到 PendingComponents
Dim Components = GetSelectedComponents()
For Each Component In {
(Loader:=LoadOptiFine, Version:=Components.OptiFineVersion),
(Loader:=LoadFabricApi, Version:=Components.FabricApiVersion),
(Loader:=LoadOptiFabric, Version:=Components.OptiFabricVersion),
(Loader:=LoadForge, Version:=SelectedForge?.VersionName),
(Loader:=LoadNeoForge, Version:=SelectedNeoForge?.VersionName),
(Loader:=LoadFabric, Version:=SelectedFabric),
(Loader:=LoadLiteLoader, Version:=SelectedLiteLoader?.JsonToken?("version").ToString())}
'保留尚未加载完成的组件选择
If Component.Version IsNot Nothing Then PendingComponents(Component.Loader) = Component.Version
Next
End If
MyBase.ShowMinecraftVersionList()
End Sub
Public Overrides Sub ReloadSelected()
If IsRestoringComponents Then Return
If IsInSelectPage AndAlso VanillaName IsNot Nothing AndAlso PendingComponents.Any() Then
IsRestoringComponents = True
Try
Dim HasProcessed As Boolean = False
'优先恢复 Mod 加载器,再匹配兼容的附加组件
For Each LoadingComponent In {LoadFabric, LoadForge, LoadNeoForge, LoadLiteLoader, LoadOptiFine, LoadFabricApi, LoadOptiFabric}
Dim Version As String = Nothing
If Not PendingComponents.TryGetValue(LoadingComponent, Version) Then Continue For
If Not LoadingComponent.State.IsLoader Then Continue For
Dim Loader = DirectCast(LoadingComponent.State, LoaderBase)
'检查前置组件
If LoadingComponent Is LoadOptiFine Then
If {LoadFabric, LoadForge, LoadNeoForge, LoadLiteLoader}.Any(Function(Component) PendingComponents.ContainsKey(Component)) Then Continue For
ElseIf LoadingComponent Is LoadFabricApi Then
If PendingComponents.ContainsKey(LoadFabric) Then Continue For
ElseIf LoadingComponent Is LoadForge Then
If Not McVersion.IsStandardFormat(VanillaName) Then
PendingComponents.Remove(LoadingComponent)
Continue For
End If
If DirectCast(Loader, LoaderTask(Of String, List(Of DlForgeVersionEntry))).Input <> VanillaName Then Continue For
If Loader.State = LoadState.Failed AndAlso Loader.Error.Message = "无" Then
PendingComponents.Remove(LoadingComponent)
Continue For
End If
ElseIf LoadingComponent Is LoadOptiFabric Then
If PendingComponents.ContainsKey(LoadFabric) OrElse PendingComponents.ContainsKey(LoadOptiFine) Then Continue For
End If
If Loader.State <> LoadState.Finished Then Continue For
'移除已处理的组件
PendingComponents.Remove(LoadingComponent)
HasProcessed = True
'匹配目标 MC 版本下兼容的组件
If LoadingComponent Is LoadFabric Then
If SelectedFabric Is Nothing AndAlso LoadFabricGetError() Is Nothing Then
Dim Versions = DlFabricListLoader.Output.Value("loader").OfType(Of JObject)
SelectedFabric = If(Versions.FirstOrDefault(Function(v) v("version").ToString = Version), Versions.FirstOrDefault())?("version").ToString()
End If
ElseIf LoadingComponent Is LoadOptiFine Then
If SelectedOptiFine Is Nothing AndAlso LoadOptiFineGetError() Is Nothing Then
Dim Versions = DlOptiFineListLoader.Output.Value.Where(Function(v) v.Inherit = VanillaName AndAlso
(SelectedForge Is Nothing OrElse IsOptiFineSuitForForge(v, SelectedForge)))
SelectedOptiFine = If(Versions.FirstOrDefault(Function(v) v.InstanceName.AfterLast("-OptiFine_") = Version),
Versions.OrderBy(Function(v) v.IsPreview).
ThenByDescending(Function(v) v.DisplayName, Comparer(Of String).Create(AddressOf McUtils.CompareVersion)).FirstOrDefault())
End If
ElseIf LoadingComponent Is LoadForge Then
If SelectedForge Is Nothing AndAlso LoadForgeGetError() Is Nothing Then
Dim Versions = DirectCast(Loader, LoaderTask(Of String, List(Of DlForgeVersionEntry))).Output.Where(
Function(v) v.Inherit = VanillaName AndAlso
v.Category <> "universal" AndAlso v.Category <> "client" AndAlso
(SelectedOptiFine Is Nothing OrElse IsOptiFineSuitForForge(SelectedOptiFine, v)))
SelectedForge = If(Versions.FirstOrDefault(Function(v) v.VersionName = Version), Versions.OrderByDescending(Function(v) v).FirstOrDefault())
End If
ElseIf LoadingComponent Is LoadNeoForge Then
If SelectedNeoForge Is Nothing AndAlso LoadNeoForgeGetError() Is Nothing Then
Dim Versions = DlNeoForgeListLoader.Output.Value.Where(Function(v) v.Inherit = VanillaName)
SelectedNeoForge = If(Versions.FirstOrDefault(Function(v) v.VersionName = Version), Versions.FirstOrDefault())
End If
ElseIf LoadingComponent Is LoadLiteLoader Then
If SelectedLiteLoader Is Nothing AndAlso LoadLiteLoaderGetError() Is Nothing Then
Dim Versions = DlLiteLoaderListLoader.Output.Value.Where(Function(v) v.Inherit = VanillaName)
SelectedLiteLoader = If(Versions.FirstOrDefault(Function(v) v.JsonToken("version").ToString = Version), Versions.FirstOrDefault())
End If
ElseIf LoadingComponent Is LoadFabricApi Then
If SelectedFabricApi Is Nothing AndAlso LoadFabricApiGetError() Is Nothing Then
Dim Versions = DlFabricApiLoader.Output.Where(Function(v) IsFabricApiCompatible(v, VanillaName))
SelectedFabricApi = If(Versions.FirstOrDefault(Function(v) v.Version = Version OrElse v.Id = Version OrElse v.Display = Version),
Versions.OrderByDescending(Function(v) v.ReleaseDate).FirstOrDefault())
End If
If SelectedFabricApi IsNot Nothing Then AutoSelectedFabricApi = True
ElseIf LoadingComponent Is LoadOptiFabric Then
If SelectedOptiFabric Is Nothing AndAlso LoadOptiFabricGetError() Is Nothing Then
Dim Versions = DlOptiFabricLoader.Output.Where(Function(v) IsOptiFabricCompatible(v, VanillaName))
SelectedOptiFabric = If(Versions.FirstOrDefault(Function(v) v.Version = Version OrElse v.Id = Version OrElse v.Display = Version),
Versions.OrderByDescending(Function(v) v.ReleaseDate).FirstOrDefault())
End If
If SelectedOptiFabric IsNot Nothing Then AutoSelectedOptiFabric = True
End If
Next
'更新组件列表
If HasProcessed Then ReloadComponentLists()
Finally
IsRestoringComponents = False
End Try
End If
'刷新当前选择与原始版本的对比
MyBase.ReloadSelected()
If InitialTarget IsNot Nothing Then RefreshComparisons()
End Sub
Private Sub RefreshComparisons()
Dim Current = GetSelectedComponents()
ImgMinecraftNew.Source = VanillaIcon
'Minecraft
Dim IsMinecraftUnchanged = InitialTarget.MinecraftVersion = Current.MinecraftVersion
ShowComparison(PanMinecraftCompare, PanMinecraftInfo, InitialTarget.MinecraftVersion, Current.MinecraftVersion,
LabMinecraftVersion.Text, BtnMinecraftRestore, True)
'OptiFine
Dim OriginalOptiFine = TryCast(BtnOptiFineRestore.Tag, DlOptiFineListEntry)
ShowComparison(PanOptiFineCompare, PanOptiFineInfo, InitialTarget.OptiFineVersion, Current.OptiFineVersion,
LabOptiFine.Text, BtnOptiFineRestore, InitialTarget.OptiFineEntry Is Nothing OrElse
IsMinecraftUnchanged AndAlso CardOptiFine.MainSwap.Visibility = Visibility.Visible AndAlso OriginalOptiFine IsNot Nothing AndAlso
(SelectedForge Is Nothing OrElse IsOptiFineSuitForForge(OriginalOptiFine, SelectedForge)), BtnOptiFineClear)
'Fabric API
ShowComparison(PanFabricApiCompare, PanFabricApiInfo, InitialTarget.FabricApiVersion, Current.FabricApiVersion,
LabFabricApi.Text, BtnFabricApiRestore, InitialTarget.FabricApiVersion Is Nothing OrElse
IsMinecraftUnchanged AndAlso SelectedFabric IsNot Nothing AndAlso CardFabricApi.MainSwap.Visibility = Visibility.Visible, BtnFabricApiClear)
'OptiFabric
ShowComparison(PanOptiFabricCompare, PanOptiFabricInfo, InitialTarget.OptiFabricVersion, Current.OptiFabricVersion,
LabOptiFabric.Text, BtnOptiFabricRestore, InitialTarget.OptiFabricVersion Is Nothing OrElse
IsMinecraftUnchanged AndAlso SelectedFabric IsNot Nothing AndAlso SelectedOptiFine IsNot Nothing AndAlso CardOptiFabric.MainSwap.Visibility = Visibility.Visible, BtnOptiFabricClear)
'Mod Loader
For Each Loader In {
(Kind:=ModLoaders.Forge, Card:=CardForge, ComparePanel:=PanForgeCompare, CurrentPanel:=PanForgeInfo, Label:=LabForge, RestoreButton:=BtnForgeRestore, ClearButton:=BtnForgeClear),
(Kind:=ModLoaders.NeoForge, Card:=CardNeoForge, ComparePanel:=PanNeoForgeCompare, CurrentPanel:=PanNeoForgeInfo, Label:=LabNeoForge, RestoreButton:=BtnNeoForgeRestore, ClearButton:=BtnNeoForgeClear),
(Kind:=ModLoaders.Fabric, Card:=CardFabric, ComparePanel:=PanFabricCompare, CurrentPanel:=PanFabricInfo, Label:=LabFabric, RestoreButton:=BtnFabricRestore, ClearButton:=BtnFabricClear),
(Kind:=ModLoaders.LiteLoader, Card:=CardLiteLoader, ComparePanel:=PanLiteLoaderCompare, CurrentPanel:=PanLiteLoaderInfo, Label:=LabLiteLoader, RestoreButton:=BtnLiteLoaderRestore, ClearButton:=BtnLiteLoaderClear)}
Dim OldVersion As String = Nothing, NewVersion As String = Nothing
InitialTarget.ModLoaderVersions.TryGetValue(Loader.Kind, OldVersion)
Current.ModLoaderVersions.TryGetValue(Loader.Kind, NewVersion)
Dim CanRestore = OldVersion Is Nothing OrElse IsMinecraftUnchanged AndAlso
Loader.Card.MainSwap.Visibility = Visibility.Visible AndAlso Loader.RestoreButton.Tag IsNot Nothing
If Loader.Kind = ModLoaders.Forge AndAlso OldVersion IsNot Nothing AndAlso CanRestore Then
CanRestore = SelectedOptiFine Is Nothing OrElse IsOptiFineSuitForForge(SelectedOptiFine, DirectCast(Loader.RestoreButton.Tag, DlForgeVersionEntry))
End If
If Loader.Kind = ModLoaders.Fabric AndAlso OldVersion IsNot Nothing Then
Dim TargetVersion = VanillaName.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3")
CanRestore = CardFabric.Visibility = Visibility.Visible AndAlso CardFabric.MainSwap.Visibility = Visibility.Visible AndAlso
DlFabricListLoader.Output.Value("loader").OfType(Of JObject).Any(Function(v) v("version").ToString = OldVersion) AndAlso
DlFabricListLoader.Output.Value("game").OfType(Of JObject).Any(Function(v) v("version").ToString = TargetVersion)
End If
ShowComparison(Loader.ComparePanel, Loader.CurrentPanel, OldVersion, NewVersion, Loader.Label.Text,
Loader.RestoreButton, CanRestore, Loader.ClearButton)
Next
UpdateStartButton()
End Sub
Private Sub ShowComparison(ComparePanel As Grid, CurrentPanel As FrameworkElement, OldVersion As String, NewVersion As String, NewText As String,
RestoreButton As MyIconButton, CanRestore As Boolean, Optional ClearButton As FrameworkElement = Nothing)
'获取不可安装的原因
Dim UnavailableReason As String = Nothing
If NewVersion Is Nothing Then
If RestoreButton Is BtnOptiFineRestore Then
UnavailableReason = LoadOptiFineGetError()
ElseIf RestoreButton Is BtnForgeRestore Then
UnavailableReason = If(McVersion.IsStandardFormat(VanillaName), LoadForgeGetError(), "无")
ElseIf RestoreButton Is BtnNeoForgeRestore Then
UnavailableReason = If(CardNeoForge.Visibility = Visibility.Collapsed, "无", LoadNeoForgeGetError())
ElseIf RestoreButton Is BtnFabricRestore Then
UnavailableReason = If(CardFabric.Visibility = Visibility.Collapsed, "无", LoadFabricGetError())
ElseIf RestoreButton Is BtnLiteLoaderRestore Then
UnavailableReason = If(CardLiteLoader.Visibility = Visibility.Collapsed, "无", LoadLiteLoaderGetError())
ElseIf RestoreButton Is BtnFabricApiRestore Then
UnavailableReason = LoadFabricApiGetError()
ElseIf RestoreButton Is BtnOptiFabricRestore Then
UnavailableReason = LoadOptiFabricGetError()
End If
NewText = If(UnavailableReason, "可以添加")
End If
'修改对比
Dim IsPending = NewVersion Is Nothing AndAlso (NewText = "获取中……" OrElse NewText.StartsWithF("获取失败:"))
Dim IsChanged = OldVersion <> NewVersion AndAlso Not IsPending
CanRestore = IsChanged AndAlso OldVersion IsNot Nothing AndAlso CanRestore
ComparePanel.Visibility = If(IsChanged, Visibility.Visible, Visibility.Collapsed)
CurrentPanel.Visibility = If(IsChanged, Visibility.Collapsed, Visibility.Visible)
'还原与取消安装按钮
RestoreButton.Visibility = If(CanRestore, Visibility.Visible, Visibility.Collapsed)
If ClearButton IsNot Nothing Then ClearButton.ToolTip = If(OldVersion Is Nothing, "取消安装", "移除")
Dim HasClearButton = ClearButton IsNot Nothing AndAlso ClearButton.Visibility = Visibility.Visible
RestoreButton.Margin = New Thickness(0, 5, If(CanRestore AndAlso HasClearButton, 60, 32), 0)
ComparePanel.Margin = New Thickness(ComparePanel.Margin.Left, ComparePanel.Margin.Top,
If(CanRestore AndAlso HasClearButton, 110, 80), ComparePanel.Margin.Bottom)
If Not IsChanged Then Return
'显示修改前已安装组件的对比卡片
If OldVersion IsNot Nothing Then
Dim Card = CType(ComparePanel.Parent, MyCard)
If Card.Visibility = Visibility.Collapsed Then
Card.IsSwapped = True
If Card.MainSwap IsNot Nothing Then Card.MainSwap.Visibility = Visibility.Collapsed
Card.Visibility = Visibility.Visible
End If
End If
Dim OldSide = CType(ComparePanel.Children(0), DockPanel)
Dim NewSide = CType(ComparePanel.Children(2), DockPanel)
OldSide.Children(0).Visibility = If(OldVersion Is Nothing, Visibility.Collapsed, Visibility.Visible)
CType(OldSide.Children(1), TextBlock).Margin = If(OldVersion Is Nothing, New Thickness(0), New Thickness(7, 0, 0, 0))
NewSide.Children(0).Visibility = If(NewVersion Is Nothing, Visibility.Collapsed, Visibility.Visible)
Dim NewLabel = CType(NewSide.Children(1), TextBlock)
NewLabel.Margin = If(NewVersion Is Nothing, New Thickness(0), New Thickness(7, 0, 0, 0))
NewLabel.Text = If(NewVersion Is Nothing, "移除", NewText)
If NewVersion Is Nothing AndAlso UnavailableReason IsNot Nothing Then
NewLabel.Inlines.Add(New Run($"({If(UnavailableReason = "无", "无可用版本", UnavailableReason)})") With {.Foreground = ColorGray4})
End If
End Sub
Private Sub RestoreComponent(sender As Object, e As EventArgs) Handles BtnMinecraftRestore.Click, BtnOptiFineRestore.Click, BtnForgeRestore.Click,
BtnNeoForgeRestore.Click, BtnFabricRestore.Click, BtnFabricApiRestore.Click, BtnOptiFabricRestore.Click, BtnLiteLoaderRestore.Click
Dim Button = DirectCast(sender, MyIconButton)
'恢复初始选择
If Button Is BtnMinecraftRestore Then
IsRestoringComponents = True
Try
PendingComponents.Clear()
MinecraftSelected(McDownloadListItem(DirectCast(Button.Tag, JObject), Nothing, False).Init(), Nothing)
If ForgeVersionLoader IsNot Nothing Then LoadForge.State = ForgeVersionLoader
'恢复初始的组件
SelectedOptiFine = TryCast(BtnOptiFineRestore.Tag, DlOptiFineListEntry)
SelectedForge = TryCast(BtnForgeRestore.Tag, DlForgeVersionEntry)
SelectedNeoForge = TryCast(BtnNeoForgeRestore.Tag, DlNeoForgeListEntry)
SelectedFabric = TryCast(BtnFabricRestore.Tag, String)
SelectedLiteLoader = TryCast(BtnLiteLoaderRestore.Tag, DlLiteLoaderListEntry)
SelectedFabricApi = TryCast(BtnFabricApiRestore.Tag, ResourceVersion)
SelectedOptiFabric = TryCast(BtnOptiFabricRestore.Tag, ResourceVersion)
AutoSelectedFabricApi = SelectedFabric IsNot Nothing
AutoSelectedOptiFabric = SelectedFabric IsNot Nothing AndAlso SelectedOptiFine IsNot Nothing
Finally
IsRestoringComponents = False
End Try
ReloadComponentLists()
Return
ElseIf Button Is BtnOptiFineRestore Then
SelectedOptiFine = TryCast(Button.Tag, DlOptiFineListEntry)
AutoSelectedOptiFabric = SelectedOptiFine IsNot Nothing AndAlso SelectedFabric IsNot Nothing
If SelectedOptiFine Is Nothing Then SelectedOptiFabric = Nothing
ElseIf Button Is BtnForgeRestore Then
SelectedForge = TryCast(Button.Tag, DlForgeVersionEntry)
ElseIf Button Is BtnNeoForgeRestore Then
SelectedNeoForge = TryCast(Button.Tag, DlNeoForgeListEntry)
ElseIf Button Is BtnFabricRestore Then
SelectedFabric = TryCast(Button.Tag, String)
AutoSelectedFabricApi = SelectedFabric IsNot Nothing
AutoSelectedOptiFabric = SelectedFabric IsNot Nothing AndAlso SelectedOptiFine IsNot Nothing
If SelectedFabric Is Nothing Then
SelectedFabricApi = Nothing
SelectedOptiFabric = Nothing
End If
ElseIf Button Is BtnFabricApiRestore Then
SelectedFabricApi = TryCast(Button.Tag, ResourceVersion)
AutoSelectedFabricApi = True
ElseIf Button Is BtnOptiFabricRestore Then
SelectedOptiFabric = TryCast(Button.Tag, ResourceVersion)
AutoSelectedOptiFabric = True
ElseIf Button Is BtnLiteLoaderRestore Then
SelectedLiteLoader = TryCast(Button.Tag, DlLiteLoaderListEntry)
End If
DirectCast(Button.Parent, MyCard).IsSwapped = True
'刷新组件
ReloadComponentLists()
End Sub
Private Sub CardMinecraft_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles CardMinecraft.MouseLeftButtonUp
ShowMinecraftVersionList()
e.Handled = True
End Sub
Private Sub UpdateStartButton() Handles TextSelectName.TextChanged, TextSelectName.ValidateChanged
BtnStart.IsEnabled = TextSelectName.IsValidated AndAlso InitialTarget IsNot Nothing AndAlso
Not PendingComponents.Any() AndAlso
(Not IsVanillaVersionReliable OrElse Not InitialTarget.IsEquivalentTo(GetSelectedComponents()) OrElse
Not String.Equals(TextSelectName.Text, OriginalName, StringComparison.Ordinal))
End Sub
Private Sub UpdateOriginalNameVisibility() Handles TextSelectName.TextChanged
Dim IsNameChanged = Not String.Equals(TextSelectName.Text, OriginalName, StringComparison.Ordinal)
Dim NewVisibility = If(IsNameChanged, Visibility.Visible, Visibility.Collapsed)
If LabSelectName.Visibility = NewVisibility Then Return
LabSelectName.Visibility = NewVisibility
BtnOriginalNameRestore.Visibility = NewVisibility
Dim RightMargin = If(IsNameChanged, 40, 16)
If IsInSelectPage AndAlso AniControlEnabled = 0 Then
AniStart(AaDouble(Sub(Delta) TextSelectName.Margin = New Thickness(56, 16, TextSelectName.Margin.Right + Delta, 0),
RightMargin - TextSelectName.Margin.Right, 150,, New AniEaseOutFluent), "DownloadModify Name Margin")
Else
AniStop("DownloadModify Name Margin")
TextSelectName.Margin = New Thickness(56, 16, RightMargin, 0)
End If
End Sub
Private Sub BtnOriginalNameRestore_Click(sender As Object, e As EventArgs) Handles BtnOriginalNameRestore.Click
TextSelectName.Text = OriginalName
IsSelectNameEdited = Not String.Equals(OriginalName, GetSelectName(), StringComparison.Ordinal)
End Sub
Public Overrides Sub StartInstall(Request As McInstallRequest)
UpdateStartButton()
If Not BtnStart.IsEnabled Then Return
Dim TargetInstance = InitLoader.Input
Dim Target = Request.Components
Dim InstanceName = Request.NewInstanceName
Dim IsNameChanged = Not String.Equals(InstanceName, TargetInstance.Name, StringComparison.Ordinal)
Request.NewInstanceName = If(IsNameChanged, InstanceName, Nothing)
Request.InstanceFolder = TargetInstance.PathVersion
'警告
Dim Warnings As New List(Of String)
If (SelectedForge IsNot Nothing OrElse SelectedNeoForge IsNot Nothing OrElse SelectedFabric IsNot Nothing) AndAlso
Not PathUtils.IsSame(TargetInstance.PathIndie, TargetInstance.PathVersion) Then
Warnings.Add("该版本尚未开启版本隔离,可能与别的 MC 版本共用同一个 Mod 和存档文件夹。" & vbCrLf &
"修改版本后,已有的 Mod、存档等需要手动迁移到新的游戏目录。")
End If
Dim IsMinecraftVersionChanged = InitialTarget.MinecraftVersion <> Target.MinecraftVersion
If Not IsVanillaVersionReliable AndAlso Not IsMinecraftVersionChanged Then
Warnings.Add($"PCL 未能精准识别该版本,请确认它是不是 Minecraft {InitialTarget.MinecraftVersion},如果不正确,请取消并选择正确的版本!")
End If
If Not Configs.HintModifyInstanceWarning.Get() Then
Warnings.Add("如果该版本安装了 PCL 不兼容的加载器,或经过第三方修改,这些改动将会丢失!")
End If
Dim IsModLoaderChanged As Boolean = False
For Each Loader In InitialTarget.ModLoaderVersions.Keys.Union(Target.ModLoaderVersions.Keys)
Dim CurrentVersion As String = Nothing, TargetVersion As String = Nothing
Dim HasCurrent = InitialTarget.ModLoaderVersions.TryGetValue(Loader, CurrentVersion)
Dim HasTarget = Target.ModLoaderVersions.TryGetValue(Loader, TargetVersion)
If HasCurrent <> HasTarget OrElse CurrentVersion <> TargetVersion Then
IsModLoaderChanged = True
Exit For
End If
Next
If (IsModLoaderChanged OrElse IsMinecraftVersionChanged) AndAlso InitialTarget.ModLoaderVersions.Any Then
Warnings.Add("现有的 Mod、资源包等在修改后可能会不兼容!")
End If
If Warnings.Any() Then
If MyMsgBox("请注意:" & vbCrLf & "• " & Warnings.Join(vbCrLf & "• "), "修改确认", "开始修改", "取消", IsWarn:=True) <> 1 Then Return
End If
If Not Configs.HintModifyInstanceWarning.Get() Then Configs.HintModifyInstanceWarning.Set(True)
'开始修改
If IsVanillaVersionReliable AndAlso InitialTarget.IsEquivalentTo(Target) Then
'仅重命名
Try
Dim OldPath = TargetInstance.PathVersion
Dim RenamedInstance = TargetInstance.Rename(InstanceName)
If PageInstanceLeft.Instance IsNot Nothing AndAlso PathUtils.IsSame(PageInstanceLeft.Instance.PathVersion, OldPath) Then PageInstanceLeft.Instance = RenamedInstance
If McInstanceSelected IsNot Nothing AndAlso PathUtils.IsSame(McInstanceSelected.PathVersion, OldPath) Then McInstanceSelected = RenamedInstance
PageInstanceLeft.ReloadCurrentJava()
Hint("重命名成功!", HintType.Green)
LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")
FrmMain.PageBack()
Catch ex As Exception
Logger.Error(ex, "重命名版本失败", LogBehavior.Alert)
End Try
Else
'实际修改
If McModificationStart(Request, TargetInstance) Then FrmMain.PageBack()
End If
End Sub
End Class
@@ -64,10 +64,10 @@ Public Class PageDownloadResourceDetail
If IsXSpecial Then Return 1
If IsYSpecial Then Return -1
'比较版本号
Dim VersionCodeSort = -CompareVersion(x.Replace(x.BeforeFirst(" ") & " ", ""), y.Replace(y.BeforeFirst(" ") & " ", ""))
Dim VersionCodeSort = -McUtils.CompareVersion(x.Replace(x.BeforeFirst(" ") & " ", ""), y.Replace(y.BeforeFirst(" ") & " ", ""))
If VersionCodeSort <> 0 Then Return VersionCodeSort
'比较全部
Return -CompareVersion(x, y)
Return -McUtils.CompareVersion(x, y)
End Function
Public Sub New(Optional Topmost As String = "")
Me.Topmost = If(Topmost, "")
@@ -98,7 +98,7 @@ Public Class PageDownloadResourceDetail
Dim UpdateFilters =
Sub()
Filters = Results.SelectMany(Function(v) v.GameVersions).Select(Function(v) GetGroupedVersionName(v, GroupedDrop, GroupedOld)).
Distinct.OrderByDescending(Function(s) s, New VersionComparer).ToList
Distinct.OrderByDescending(Function(s) s, Comparer(Of String).Create(Function(x, y) McUtils.CompareVersion(x, y))).ToList
End Sub
'确定分组方式
@@ -174,11 +174,8 @@ GroupDone:
Dim Loaders As New List(Of String)
If Project.ModLoaders.Flags.Count > 1 AndAlso '工程至少有两个加载器
Version.ResourceType = ResourceTypes.Mod AndAlso '是 Mod
McVersion.IsFormatFit(VerName) Then '不是 “快照版本” 之类的
For Each Loader In Version.ModLoaders.Flags
If Loader = ModLoaders.Quilt AndAlso Settings.Get(Of Boolean)("ToolDownloadIgnoreQuilt") Then Continue For
Loaders.Add(Loader.ToString & " ")
Next
McVersion.IsStandardFormat(VerName) Then '不是 “快照版本” 之类的
Loaders.AddRange(Version.ModLoaders.Flags.Select(Function(f) f.ToString & " "))
End If
If Not Loaders.Any() Then Loaders.Add("") '保底加一个空的,确保它在一张卡片里
'实际添加
@@ -209,16 +206,17 @@ GroupDone:
If Not Pair.Value.Any() Then Continue For
If Pair.Key = TargetCardName.Replace("(所选版本)", "") Then Continue For
'增加卡片
Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15), .SwapType = If(TargetResourceType = ResourceTypes.ModPack, 9, 8)} '9 是安装,8 是另存为
Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15)}
Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value}
NewCard.Children.Add(NewStack)
NewCard.SwapControl = NewStack
NewCard.SetContentBuilder(AddressOf InitializeStackResourceVersions)
PanResults.Children.Add(NewCard)
'确定卡片是否默认展开
If Pair.Key = TargetCardName OrElse
(FrmMain.PageCurrent.Additional IsNot Nothing AndAlso '#2761
CType(FrmMain.PageCurrent.Additional(1), List(Of String)).Contains(NewCard.Title)) Then
MyCard.StackInstall(NewStack, If(TargetResourceType = ResourceTypes.ModPack, 9, 8), Pair.Key) '9 是安装,8 是另存为
NewCard.BuildContent()
Else
NewCard.IsSwapped = True
End If
@@ -243,13 +241,53 @@ GroupDone:
Logger.Error(ex, "可视化工程下载列表出错")
End Try
End Sub
''' <summary>
''' 根据卡片中的 StackPanel.Tag 将资源版本列表数据实例化为 UI。
''' </summary>
Private Sub InitializeStackResourceVersions(Stack As StackPanel)
Dim Versions = CType(Stack.Tag, List(Of ResourceVersion)).SortByComparison(Function(a, b) a.ReleaseDate > b.ReleaseDate)
Dim HasDuplicates = Versions.DistinctBy(Function(a) a.Display).Count <> Versions.Count
Dim Dependences As List(Of String)
'获取并添加前置信息
If TargetResourceType = ResourceTypes.ModPack Then
Dependences = New List(Of String) '整合包的前置信息会显示其中包含的一大堆 Mod,不是真正意义的前置
Else
Dependences = Versions.SelectMany(Function(f) f.Dependencies).Distinct.Ordered.ToList()
End If
If Dependences.Any() Then
Dependences = Dependences.Where(
Function(d)
If Not ResourceProject.Cache.ContainsKey(d) Then Logger.Warn($"未找到 ID {d} 的前置信息")
Return ResourceProject.Cache.ContainsKey(d)
End Function).ToList
'添加开头间隔
Stack.Children.Add(New TextBlock With {.Text = "前置资源", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 2, 0, 5)})
'添加前置列表
For Each Dep In Dependences
Dim Item = ResourceProject.Cache(Dep).ToResourceItem(False, False)
Stack.Children.Add(Item)
Next
'添加结尾间隔
Stack.Children.Add(New TextBlock With {.Text = "版本列表", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 12, 0, 5)})
End If
'实例化每个元素
For Each Version In Versions
If Not RestrictMcDownload AndAlso TargetResourceType = ResourceTypes.ModPack Then
Stack.Children.Add(Version.ToListItem(AddressOf Install_Click, AddressOf Save_Click, IsBadDisplay:=HasDuplicates))
Else
Stack.Children.Add(Version.ToListItem(AddressOf Save_Click, IsBadDisplay:=HasDuplicates))
End If
Next
Stack.Children.Add(New FrameworkElement With {.Height = 18})
Stack.Tag = Nothing
End Sub
Private Function GetGroupedVersionName(Name As String, GroupedByDrop As Boolean, FoldOld As Boolean) As String
Dim Drop = McVersion.VersionToDrop(Name)
If Name Is Nothing Then
Return "其他"
ElseIf Name.Contains("w") OrElse Drop = 209 Then
Return "快照版"
ElseIf Not McVersion.IsFormatFit(Name) OrElse (FoldOld AndAlso Drop < 120) Then
ElseIf Not McVersion.IsStandardFormat(Name) OrElse (FoldOld AndAlso Drop < 120) Then
Return "远古版"
ElseIf GroupedByDrop Then
Return McVersion.DropToVersion(Drop)
@@ -329,7 +367,7 @@ GroupDone:
Loader.Start(McFolderSelected & "versions\" & InstanceName & "\")
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
FrmMain.BtnExtraDownload.Ribble()
FrmMain.PageChange(FormMain.PageType.TaskManager)
Catch ex As Exception
Logger.Error(ex, "下载资源整合包失败")
@@ -472,19 +510,23 @@ GroupDone:
'弹窗要求选择保存位置
Dim Target As String
Dim Ext As String = If(File.ResourceType = ResourceTypes.Mod,
If(File.FileName.EndsWithF(".litemod"), "litemod", "jar"),
If(File.FileName.EndsWithF(".mrpack"), "mrpack", "zip"))
If(PathUtils.GetExtension(File.FileName) = "litemod", "litemod", "jar"),
If(PathUtils.GetExtension(File.FileName) = "mrpack", "mrpack", "zip"))
Target = Dialogs.SaveFile("选择保存位置", FileName, DefaultFolder, {(Ext, Desc & "文件")})
If Target Is Nothing Then Return
'构造步骤加载器
Dim LoaderName As String = Desc & "下载:" & PathUtils.GetFileNameWithoutExtension(Target) & " "
If Target <> DefaultFolder Then CachedFolder(File.ResourceType) = PathUtils.RemoveLastPart(Target)
If Not PathUtils.IsSame(PathUtils.RemoveLastPart(Target), DefaultFolder) Then CachedFolder(File.ResourceType) = PathUtils.RemoveLastPart(Target)
Dim Loaders As New List(Of LoaderBase)
Loaders.Add(New LoaderDownload("下载文件", {
File.ToNetFile(Target, If(TargetResourceType = ResourceTypes.Any, ResourceVersion.DownloadReason.Dependency, ResourceVersion.DownloadReason.Standalone))
}) With {.ProgressWeight = 6, .Block = True})
'启动
Dim Loader As New LoaderCombo(Of Integer)(LoaderName, Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
Dim Loader As New LoaderCombo(Of Integer)(LoaderName, Loaders) With {.OnStateChanged =
Sub(MyLoader)
LoaderStateChangedHintOnly(MyLoader)
If MyLoader.State = LoadState.Finished AndAlso File.ResourceType = ResourceTypes.ModPack Then OpenExplorer(Target)
End Sub}
Loader.Start(1)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
@@ -513,29 +555,4 @@ GroupDone:
PageLoaderRestart(IsForceRestart:=True)
End Sub
''' <summary>
''' 预载包含大量 ProjectVersion 的卡片,添加必要的元素和前置列表。
''' </summary>
Public Shared Sub ResourceFilesCardPreload(Stack As StackPanel, Files As List(Of ResourceVersion))
'获取卡片对应的前置 ID
'如果为整合包就不会有 Dependencies 信息,所以不用管
Dim Deps As List(Of String) = Files.SelectMany(Function(f) f.Dependencies).Distinct.ToList()
Deps.Sort()
If Not Deps.Any() Then Return
Deps = Deps.Where(
Function(dep)
If Not ResourceProject.Cache.ContainsKey(dep) Then Logger.Warn($"未找到 ID {dep} 的前置信息")
Return ResourceProject.Cache.ContainsKey(dep)
End Function).ToList
'添加开头间隔
Stack.Children.Add(New TextBlock With {.Text = "前置资源", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 2, 0, 5)})
'添加前置列表
For Each Dep In Deps
Dim Item = ResourceProject.Cache(Dep).ToResourceItem(False, False)
Stack.Children.Add(Item)
Next
'添加结尾间隔
Stack.Children.Add(New TextBlock With {.Text = "版本列表", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 12, 0, 5)})
End Sub
End Class
@@ -34,6 +34,7 @@
<TextBlock VerticalAlignment="Center" Grid.Row="2" HorizontalAlignment="Left" Text="版本" Margin="0,0,18,0" />
<local:MyComboBox x:Name="TextSearchVersion" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" IsEditable="True" IsTextSearchEnabled="False">
<local:MyComboBoxItem Content="全部 (也可自行输入)" IsSelected="True" />
<local:MyComboBoxItem Content="26.2" />
<local:MyComboBoxItem Content="26.1" />
<local:MyComboBoxItem Content="1.21.11" />
<local:MyComboBoxItem Content="1.21.8" />
@@ -51,7 +52,6 @@
<local:MyComboBoxItem Content="Forge" Tag="1" />
<local:MyComboBoxItem Content="NeoForge" Tag="16" />
<local:MyComboBoxItem Content="Fabric" Tag="4" />
<local:MyComboBoxItem Content="Quilt" Tag="8" />
<local:MyComboBoxItem Content="LiteLoader" Tag="2" />
</local:MyComboBox>
<TextBlock VerticalAlignment="Center" Grid.Column="4" Grid.Row="2" HorizontalAlignment="Left" Text="类型" Margin="0,0,18,0" />
@@ -90,11 +90,11 @@ Public Class PageResource
Next
Return ComboSearchLoader.Items(0)
End Function
If TargetInstance.Version.HasForge Then
If TargetInstance.Version.ModLoaders.HasFlagF(ModLoaders.Forge) Then
ComboSearchLoader.SelectedItem = GetTargetItemByName("Forge")
ElseIf TargetInstance.Version.HasFabric Then
ElseIf TargetInstance.Version.ModLoaders.HasFlagF(ModLoaders.Fabric) Then
ComboSearchLoader.SelectedItem = GetTargetItemByName("Fabric")
ElseIf TargetInstance.Version.HasNeoForge Then
ElseIf TargetInstance.Version.ModLoaders.HasFlagF(ModLoaders.NeoForge) Then
ComboSearchLoader.SelectedItem = GetTargetItemByName("NeoForge")
End If
TargetInstance = Nothing
@@ -335,7 +335,7 @@ Public Class MyLocalModItem
BtnUpdate.Visibility = Visibility.Collapsed
End If
'标题与描述
Dim DescFileName As String = Entry.File.Name.BeforeLast(".")
Dim DescFileName As String = PathUtils.GetFileNameWithoutExtension(Entry.EnabledName)
Dim NewDescription As String
If Settings.Get(Of Integer)("ToolModLocalNameStyle") = 1 Then
'标题显示文件名,详情显示译名
@@ -181,6 +181,18 @@
RequireModLoaderOrOptiFine="True" />
</local:MyCheckBox.Tag>
</local:MyCheckBox>
<StackPanel Margin="30,0,0,0"
Visibility="{Binding Checked, ElementName=CheckOptionsShaderPacks, Converter={StaticResource BooleanToVisibilityConverter}}">
<local:MyCheckBox x:Name="CheckOptionsShaderPackSettings">
<local:MyCheckBox.Tag>
<local:ExportOption
Title="光影包设置"
Description="已选光影包的自定义设置"
ShowRules="shaderpacks/*.txt"
DefaultChecked="True" />
</local:MyCheckBox.Tag>
</local:MyCheckBox>
</StackPanel>
<StackPanel Margin="30,0,0,0" x:Name="PanOptionsShaderPacks"
Visibility="{Binding Checked, ElementName=CheckOptionsShaderPacks, Converter={StaticResource BooleanToVisibilityConverter}}">
</StackPanel>
@@ -22,14 +22,14 @@ Public Class PageInstanceExport
End Sub
Private Sub PageInstanceExport_Loaded() Handles Me.Loaded
AniControlEnabled += 1
If CurrentInstance <> PageInstanceLeft.Instance.PathVersion Then RefreshAll() '切换到了另一个版本,重置页面
If Not PathUtils.IsSame(CurrentInstance, PageInstanceLeft.Instance.PathVersion) Then RefreshAll() '切换到了另一个版本,重置页面
CustomEventService.SetEventData(BtnAdvancedHelp,
If(BuildType = BuildTypes.Release, "指南/整合包制作 - Public.json", "指南/整合包制作 - Snapshot.json"))
AniControlEnabled -= 1
End Sub
Public Sub RefreshAll() Implements IRefreshable.Refresh
Logger.Info("刷新导出页面")
HintOptiFine.Visibility = If(PageInstanceLeft.Instance.Version.HasOptiFine, Visibility.Visible, Visibility.Collapsed)
HintOptiFine.Visibility = If(PageInstanceLeft.Instance.Version.HasOptiFine AndAlso Not PageInstanceLeft.Instance.Version.OptiFineAsMod, Visibility.Visible, Visibility.Collapsed)
CurrentInstance = PageInstanceLeft.Instance.PathVersion
TextExportName.Text = ""
TextExportName.HintText = PageInstanceLeft.Instance.Name
@@ -76,6 +76,7 @@ Public Class PageInstanceExport
If Not SubFolder.EnumerateFileSystemInfos().Any() Then Continue For
Dim NewCheckBox As New MyCheckBox With {. _
Tag = New ExportOption With {.Title = SubFolder.Name, .DefaultChecked = True, .Rules = StringUtils.LikePatternEscape($"{Folder}/{SubFolder.Name}/")}}
If AcceptCompressedFile Then GetExportOption(NewCheckBox).Description = "文件夹"
If Panel Is PanOptionsSaves Then GetExportOption(NewCheckBox).Description = SubFolder.LastWriteTime.ToString("yyyy'/'MM'/'dd HH':'mm")
Panel.Children.Add(NewCheckBox)
Next
@@ -113,7 +114,7 @@ Public Class PageInstanceExport
'确认选项是否应该被显示
Dim IsVisible =
Function(TargetOption As ExportOption) As Boolean
'检查需要 OptiFine 或 Mod 加载器
'检查需要 OptiFine(例如光影)或 Mod 加载器(例如 mods/)
If TargetOption.RequireOptiFine AndAlso Not PageInstanceLeft.Instance.Version.HasOptiFine Then Return False
If TargetOption.RequireModLoader AndAlso Not PageInstanceLeft.Instance.Modable Then Return False
If TargetOption.RequireModLoaderOrOptiFine AndAlso Not PageInstanceLeft.Instance.Version.HasOptiFine AndAlso Not PageInstanceLeft.Instance.Modable Then Return False
@@ -249,9 +250,22 @@ Public Class PageInstanceExport
For Each CheckBox In GetAllOptions(False)
If Not CheckBox.Checked Then Continue For
Dim TargetOption = GetExportOption(CheckBox)
If TargetOption.Rules Is Nothing Then Continue For
Dim Rules = If(TargetOption.Rules?.Split("|"c).ToList(), New List(Of String))
'动态 Rules
If CheckBox Is CheckOptionsShaderPackSettings Then '光影包设置:只导出勾选的光影包的设置
For Each ShaderPackCheckBox In PanOptionsShaderPacks.Children.OfType(Of MyCheckBox)()
If Not ShaderPackCheckBox.Checked Then Continue For
Dim ShaderPackOption = GetExportOption(ShaderPackCheckBox)
Dim SettingsFile = $"shaderpacks/{ShaderPackOption.Title}.txt"
If Not FileUtils.Exists(Path.Combine(PageInstanceLeft.Instance.PathIndie, SettingsFile)) Then Continue For
Rules.Add(StringUtils.LikePatternEscape(SettingsFile))
Next
End If
If Not Rules.Any() Then Continue For
Yield $"# {TargetOption.Title}"
For Each Rule In TargetOption.Rules.Split("|"c)
For Each Rule In Rules
Yield Rule
Next
Yield ""
@@ -412,7 +426,7 @@ Public Class PageInstanceExport
Dim LoaderName As String = $"导出整合包:" & PackName
For Each OngoingLoader In LoaderTaskbar
If OngoingLoader.Name <> LoaderName Then Continue For
FrmMain.PageChange(FormMain.PageType.DownloadManager)
FrmMain.PageChange(FormMain.PageType.TaskManager)
Return
Next
@@ -443,11 +457,11 @@ Public Class PageInstanceExport
'缓存所需参数
Dim CacheFolder = RequestTaskTempFolder()
Dim OverridesFolder = CacheFolder & "modpack\overrides\"
Dim McVersion = PageInstanceLeft.Instance
Dim TargetInstance = PageInstanceLeft.Instance
Dim IncludeJava As Boolean =
(RulesOverrides Is Nothing AndAlso CheckOptionsJava.Visibility = Visibility.Visible AndAlso CheckOptionsJava.Checked) OrElse
(RulesOverrides IsNot Nothing AndAlso CheckOptionsJava.Checked AndAlso Settings.Get(Of Integer)("VersionArgumentJavaV2", McVersion) = 2)
Dim PathIndie As String = McVersion.PathIndie
(RulesOverrides IsNot Nothing AndAlso CheckOptionsJava.Checked AndAlso Settings.Get(Of Integer)("VersionArgumentJavaV2", TargetInstance) = 2)
Dim PathIndie As String = TargetInstance.PathIndie
Dim CheckHostedAssets As Boolean = Not CheckAdvancedInclude.Checked
Dim ModrinthUploadMode As Boolean = CheckAdvancedModrinth.Checked
Dim IncludePCL As Boolean = CheckOptionsPcl.Checked
@@ -484,7 +498,7 @@ Public Class PageInstanceExport
'文件夹:进一步搜索
For Each SubFolder In Folder.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
'跳过部分又没用文件又多的文件夹,加快搜索
If {"assets", "versions", "libraries"}.Contains(SubFolder.Name) AndAlso PathIndie = PathUtils.RemoveExtendedPrefix(Folder.FullName) Then Continue For
If {"assets", "versions", "libraries"}.Contains(SubFolder.Name) AndAlso PathUtils.IsSame(PathIndie, Folder.FullName) Then Continue For
If {"structureCacheV1", ".fabric", ".git", "avatar-cache", "cosmetic-cache"}.Contains(SubFolder.Name) Then Continue For
SearchFolder(SubFolder)
Next
@@ -522,10 +536,10 @@ Public Class PageInstanceExport
Loader.Progress = 0.85
'复制 Java
If IncludeJava Then
Dim JavaToExport = SelectOrDownloadJava(McVersion, False, Loader.CreateCancellationToken, Loader.CreateSyncProgressProvider(0.85, 0.93))
Dim JavaToExport = SelectOrDownloadJava(TargetInstance, False, Loader.CreateCancellationToken, Loader.CreateSyncProgressProvider(0.85, 0.93))
If JavaToExport Is Nothing Then Throw New FileNotFoundException("版本文件夹中没有找到可导出的 Java。")
Dim JavaFolder = PathUtils.RemoveLastPart(JavaToExport.Folder)
Dim DestFolder As String = Path.Combine(OverridesFolder, JavaFolder.AfterFirst(McVersion.PathVersion, True))
Dim DestFolder As String = Path.Combine(OverridesFolder, JavaFolder.AfterFirst(TargetInstance.PathVersion, True))
DirectoryUtils.Copy(JavaFolder, DestFolder)
Logger.Info($"已复制版本文件夹中的 Java:{JavaFolder} → {DestFolder}")
End If
@@ -549,7 +563,7 @@ Public Class PageInstanceExport
Next
Loader.Progress = 0.97
'复制 PCL 版本设置
If DirectoryUtils.Exists(McVersion.PathVersion & "PCL\") Then DirectoryUtils.Copy(McVersion.PathVersion & "PCL\", OverridesFolder & "PCL\")
If DirectoryUtils.Exists(TargetInstance.PathVersion & "PCL\") Then DirectoryUtils.Copy(TargetInstance.PathVersion & "PCL\", OverridesFolder & "PCL\")
WriteIni(OverridesFolder & "PCL\Setup.ini", "IsStar", False)
'复制 PCL 本体(正式版)
If BuildType = BuildTypes.Release AndAlso IncludePCL Then FileUtils.Copy(PathExe, CacheFolder & "Plain Craft Launcher.exe")
@@ -608,7 +622,7 @@ Public Class PageInstanceExport
Try
If ModrinthUploadMode Then Return 'Modrinth 上传模式下,不从 CurseForge 获取信息
Dim CurseForgeHashes = Loader.Input.Select(Function(m) m.CurseForgeHash)
Dim CurseForgeRaw As JContainer = DlModRequest("https://api.curseforge.com/v1/fingerprints/432/", HttpMethod.Post,
Dim CurseForgeRaw As JContainer = DlModRequest("https://api.curseforge.com/v1/fingerprints/432", HttpMethod.Post,
$"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")("data")("exactMatches")
For Each ResultJson As JObject In CurseForgeRaw
If Not ResultJson.ContainsKey("file") Then Continue For
@@ -672,16 +686,16 @@ Public Class PageInstanceExport
Next
Loader.Progress = 0.2
'导出最终 JSON 文件
Dim Dependencies As New JObject From {{"minecraft", McVersion.Version.VanillaName}}
If McVersion.Version.HasForge Then Dependencies.Add("forge", McVersion.Version.Forge)
If McVersion.Version.HasFabric Then Dependencies.Add("fabric-loader", McVersion.Version.Fabric)
If McVersion.Version.HasNeoForge Then Dependencies.Add("neoforge", McVersion.Version.NeoForge)
Dim Dependencies As New JObject From {{"minecraft", TargetInstance.Version.VanillaName}}
If TargetInstance.Version.ModLoaders.HasFlagF(ModLoaders.Forge) Then Dependencies.Add("forge", TargetInstance.Version.ModLoaderVersions(ModLoaders.Forge))
If TargetInstance.Version.ModLoaders.HasFlagF(ModLoaders.Fabric) Then Dependencies.Add("fabric-loader", TargetInstance.Version.ModLoaderVersions(ModLoaders.Fabric))
If TargetInstance.Version.ModLoaders.HasFlagF(ModLoaders.NeoForge) Then Dependencies.Add("neoforge", TargetInstance.Version.ModLoaderVersions(ModLoaders.NeoForge))
Dim ResultJson As New JObject From {
{"game", "minecraft"},
{"formatVersion", 1},
{"versionId", PackVersion},
{"name", PackName},
{"summary", McVersion.Info},
{"summary", TargetInstance.Info},
{"files", Files},
{"dependencies", Dependencies}
}
@@ -712,7 +726,7 @@ Public Class PageInstanceExport
LoaderTaskbarAdd(MainLoader)
FrmMain.BtnExtraDownload.ShowRefresh()
FrmMain.BtnExtraDownload.Ribble()
FrmMain.PageChange(FormMain.PageType.DownloadManager)
FrmMain.PageChange(FormMain.PageType.TaskManager)
End Sub
#End Region
@@ -13,21 +13,29 @@
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemMod" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="2" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="Mod 管理"
Logo="M789.504 912.896h-195.072a35.328 35.328 0 0 1-34.816-35.328v-77.312a39.936 39.936 0 0 0-40.448-39.936H481.28a40.448 40.448 0 0 0-40.448 39.936v77.312a34.816 34.816 0 0 1-34.816 35.328H204.8a41.984 41.984 0 0 1-40.448-42.496v-200.192a35.328 35.328 0 0 1 35.328-35.328h72.704a39.936 39.936 0 0 0 39.936-39.936v-37.888a39.936 39.936 0 0 0-39.936-40.448H199.68a35.328 35.328 0 0 1-35.328-35.328V287.744A41.984 41.984 0 0 1 204.8 245.76h176.64v-32.768a102.4 102.4 0 0 1 102.4-102.4h33.792a102.4 102.4 0 0 1 102.4 102.4v32.768h170.496a41.984 41.984 0 0 1 41.984 41.984V460.8h28.672a102.4 102.4 0 0 1 102.4 102.4v33.792a102.4 102.4 0 0 1-102.4 102.4h-28.672V870.4a41.984 41.984 0 0 1-43.008 42.496z m-159.744-70.144h131.584V665.6a34.304 34.304 0 0 1 34.816-34.816h64a31.744 31.744 0 0 0 31.744-31.744V563.2a31.744 31.744 0 0 0-31.744-31.744h-64a34.816 34.816 0 0 1-34.816-35.328V316.416h-177.152a35.328 35.328 0 0 1-35.328-35.328V212.992a31.744 31.744 0 0 0-31.744-31.744h-33.792a31.744 31.744 0 0 0-31.744 31.744v68.096a35.328 35.328 0 0 1-34.816 35.328H234.496v130.048h37.888a110.592 110.592 0 0 1 110.08 110.592v37.888a110.592 110.592 0 0 1-110.08 110.592h-37.888v137.216h136.192v-42.496a110.592 110.592 0 0 1 110.592-110.08h37.888a110.592 110.592 0 0 1 110.592 110.08z" >
<local:MyListItem x:Name="ItemModify" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="2" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="修改" LogoScale="0.95"
Logo="F1 M652.624 145.904c62.256-62.272 163.2-62.272 225.472 0 62.272 62.256 62.272 163.2 0 225.472L477.808 771.664a28.8 28.8 0 0 1-10.816 6.8L242.4 857.376c-47.04 16.544-92.32-28.736-75.776-75.776l78.896-224.592c1.44-4.08 3.76-7.776 6.816-10.816L652.624 145.904z M828.604 195.396a89.44 89.44 0 0 0-126.488 0L308.48 589.032 241.68 781.12 433.768 714.32 828.604 321.884a89.44 89.44 0 0 0 0-126.488z M606.256 194.816a35 35 0 0 1 49.504 0l173.728 173.728a35 35 0 0 1-49.504 49.504L606.256 244.32a35 35 0 0 1 0-49.504z M261.984 539.104a35 35 0 0 1 49.504 0l173.728 173.728a35 35 0 0 1-49.504 49.504L261.984 588.608a35 35 0 0 1 0-49.504z" >
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="2" ToolTip="刷新" Click="Refresh_Click" LogoScale="0.85" Logo="M512.0 838.3c-80.2 0-153.4-29.3-210.2-77.4l75.5-75.5c11.5-11.5 25.8-22.0 25.8-37.0a27.2 27.2 0 0 0-27.1-27.1H104.0c-27.1 0-27.1 23.9-27.1 27.1v271.9a27.1 27.1 0 0 0 27.1 27.1c15.0 0 27.8-16.6 42.5-31.2l77.9-77.9c76.6 67.7 177.1 108.9 287.4 108.9 221.7 0 404.5-166.0 431.2-380.6h-109.8c-25.9 154.2-159.7 271.9-321.3 271.9zM919.9 76.6c-15.0 0-27.8 16.6-42.5 31.3L799.5 185.8c-76.5-67.7-177.1-108.9-287.4-108.9-221.8 0-404.5 166.1-431.3 380.6H190.6c25.9-154.2 159.7-271.9 321.4-271.9 80.2 0 153.4 29.3 210.1 77.4l-75.5 75.5c-11.6 11.5-25.8 22.0-25.8 37.1a27.2 27.2 0 0 0 27.1 27.1h271.9c27.1 0 27.1-23.9 27.1-27.1V103.8a27.1 27.1 0 0 0-27.1-27.1z" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemModDisabled" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="3" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="Mod 管理"
<local:MyListItem x:Name="ItemMod" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="3" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="Mod 管理"
Logo="M789.504 912.896h-195.072a35.328 35.328 0 0 1-34.816-35.328v-77.312a39.936 39.936 0 0 0-40.448-39.936H481.28a40.448 40.448 0 0 0-40.448 39.936v77.312a34.816 34.816 0 0 1-34.816 35.328H204.8a41.984 41.984 0 0 1-40.448-42.496v-200.192a35.328 35.328 0 0 1 35.328-35.328h72.704a39.936 39.936 0 0 0 39.936-39.936v-37.888a39.936 39.936 0 0 0-39.936-40.448H199.68a35.328 35.328 0 0 1-35.328-35.328V287.744A41.984 41.984 0 0 1 204.8 245.76h176.64v-32.768a102.4 102.4 0 0 1 102.4-102.4h33.792a102.4 102.4 0 0 1 102.4 102.4v32.768h170.496a41.984 41.984 0 0 1 41.984 41.984V460.8h28.672a102.4 102.4 0 0 1 102.4 102.4v33.792a102.4 102.4 0 0 1-102.4 102.4h-28.672V870.4a41.984 41.984 0 0 1-43.008 42.496z m-159.744-70.144h131.584V665.6a34.304 34.304 0 0 1 34.816-34.816h64a31.744 31.744 0 0 0 31.744-31.744V563.2a31.744 31.744 0 0 0-31.744-31.744h-64a34.816 34.816 0 0 1-34.816-35.328V316.416h-177.152a35.328 35.328 0 0 1-35.328-35.328V212.992a31.744 31.744 0 0 0-31.744-31.744h-33.792a31.744 31.744 0 0 0-31.744 31.744v68.096a35.328 35.328 0 0 1-34.816 35.328H234.496v130.048h37.888a110.592 110.592 0 0 1 110.08 110.592v37.888a110.592 110.592 0 0 1-110.08 110.592h-37.888v137.216h136.192v-42.496a110.592 110.592 0 0 1 110.592-110.08h37.888a110.592 110.592 0 0 1 110.592 110.08z" >
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="3" ToolTip="刷新" Click="Refresh_Click" LogoScale="0.85" Logo="M512.0 838.3c-80.2 0-153.4-29.3-210.2-77.4l75.5-75.5c11.5-11.5 25.8-22.0 25.8-37.0a27.2 27.2 0 0 0-27.1-27.1H104.0c-27.1 0-27.1 23.9-27.1 27.1v271.9a27.1 27.1 0 0 0 27.1 27.1c15.0 0 27.8-16.6 42.5-31.2l77.9-77.9c76.6 67.7 177.1 108.9 287.4 108.9 221.7 0 404.5-166.0 431.2-380.6h-109.8c-25.9 154.2-159.7 271.9-321.3 271.9zM919.9 76.6c-15.0 0-27.8 16.6-42.5 31.3L799.5 185.8c-76.5-67.7-177.1-108.9-287.4-108.9-221.8 0-404.5 166.1-431.3 380.6H190.6c25.9-154.2 159.7-271.9 321.4-271.9 80.2 0 153.4 29.3 210.1 77.4l-75.5 75.5c-11.6 11.5-25.8 22.0-25.8 37.1a27.2 27.2 0 0 0 27.1 27.1h271.9c27.1 0 27.1-23.9 27.1-27.1V103.8a27.1 27.1 0 0 0-27.1-27.1z" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
<local:MyListItem x:Name="ItemModDisabled" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="4" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="Mod 管理"
Logo="M789.504 912.896h-195.072a35.328 35.328 0 0 1-34.816-35.328v-77.312a39.936 39.936 0 0 0-40.448-39.936H481.28a40.448 40.448 0 0 0-40.448 39.936v77.312a34.816 34.816 0 0 1-34.816 35.328H204.8a41.984 41.984 0 0 1-40.448-42.496v-200.192a35.328 35.328 0 0 1 35.328-35.328h72.704a39.936 39.936 0 0 0 39.936-39.936v-37.888a39.936 39.936 0 0 0-39.936-40.448H199.68a35.328 35.328 0 0 1-35.328-35.328V287.744A41.984 41.984 0 0 1 204.8 245.76h176.64v-32.768a102.4 102.4 0 0 1 102.4-102.4h33.792a102.4 102.4 0 0 1 102.4 102.4v32.768h170.496a41.984 41.984 0 0 1 41.984 41.984V460.8h28.672a102.4 102.4 0 0 1 102.4 102.4v33.792a102.4 102.4 0 0 1-102.4 102.4h-28.672V870.4a41.984 41.984 0 0 1-43.008 42.496z m-159.744-70.144h131.584V665.6a34.304 34.304 0 0 1 34.816-34.816h64a31.744 31.744 0 0 0 31.744-31.744V563.2a31.744 31.744 0 0 0-31.744-31.744h-64a34.816 34.816 0 0 1-34.816-35.328V316.416h-177.152a35.328 35.328 0 0 1-35.328-35.328V212.992a31.744 31.744 0 0 0-31.744-31.744h-33.792a31.744 31.744 0 0 0-31.744 31.744v68.096a35.328 35.328 0 0 1-34.816 35.328H234.496v130.048h37.888a110.592 110.592 0 0 1 110.08 110.592v37.888a110.592 110.592 0 0 1-110.08 110.592h-37.888v137.216h136.192v-42.496a110.592 110.592 0 0 1 110.592-110.08h37.888a110.592 110.592 0 0 1 110.592 110.08z" />
<local:MyListItem x:Name="ItemExport" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="4" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="导出"
<local:MyListItem x:Name="ItemExport" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="5" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="导出"
Logo="M511 995a128 128 0 0 1-57-13L70 791a126 126 0 0 1-70-113V311a126 126 0 0 1 15-60V248c1-2 3-5 5-8a127 127 0 0 1 49-42L454 13a128 128 0 0 1 112 0l383 190a126 126 0 0 1 72 113v360a126 126 0 0 1-70 115L568 984c-17 7-37 11-57 11z m42-470v370l360-178c14-7 23-21 23-38v-335L554 524zM85 330v347a42 42 0 0 0 23 38l360 178V523L85 330zM135 260l375 189 137-65L286 188 135 260z m245-118l363 197 150-71-365-180a42 42 0 0 0-37 0l-111 53z">
<local:MyListItem.Buttons>
<x:Array Type="{x:Type local:MyIconButton}">
<local:MyIconButton Tag="4" ToolTip="刷新" Click="Refresh_Click" LogoScale="0.85" Logo="M512.0 838.3c-80.2 0-153.4-29.3-210.2-77.4l75.5-75.5c11.5-11.5 25.8-22.0 25.8-37.0a27.2 27.2 0 0 0-27.1-27.1H104.0c-27.1 0-27.1 23.9-27.1 27.1v271.9a27.1 27.1 0 0 0 27.1 27.1c15.0 0 27.8-16.6 42.5-31.2l77.9-77.9c76.6 67.7 177.1 108.9 287.4 108.9 221.7 0 404.5-166.0 431.2-380.6h-109.8c-25.9 154.2-159.7 271.9-321.3 271.9zM919.9 76.6c-15.0 0-27.8 16.6-42.5 31.3L799.5 185.8c-76.5-67.7-177.1-108.9-287.4-108.9-221.8 0-404.5 166.1-431.3 380.6H190.6c25.9-154.2 159.7-271.9 321.4-271.9 80.2 0 153.4 29.3 210.1 77.4l-75.5 75.5c-11.6 11.5-25.8 22.0-25.8 37.1a27.2 27.2 0 0 0 27.1 27.1h271.9c27.1 0 27.1-23.9 27.1-27.1V103.8a27.1 27.1 0 0 0-27.1-27.1z" />
<local:MyIconButton Tag="5" ToolTip="刷新" Click="Refresh_Click" LogoScale="0.85" Logo="M512.0 838.3c-80.2 0-153.4-29.3-210.2-77.4l75.5-75.5c11.5-11.5 25.8-22.0 25.8-37.0a27.2 27.2 0 0 0-27.1-27.1H104.0c-27.1 0-27.1 23.9-27.1 27.1v271.9a27.1 27.1 0 0 0 27.1 27.1c15.0 0 27.8-16.6 42.5-31.2l77.9-77.9c76.6 67.7 177.1 108.9 287.4 108.9 221.7 0 404.5-166.0 431.2-380.6h-109.8c-25.9 154.2-159.7 271.9-321.3 271.9zM919.9 76.6c-15.0 0-27.8 16.6-42.5 31.3L799.5 185.8c-76.5-67.7-177.1-108.9-287.4-108.9-221.8 0-404.5 166.1-431.3 380.6H190.6c25.9-154.2 159.7-271.9 321.4-271.9 80.2 0 153.4 29.3 210.1 77.4l-75.5 75.5c-11.6 11.5-25.8 22.0-25.8 37.1a27.2 27.2 0 0 0 27.1 27.1h271.9c27.1 0 27.1-23.9 27.1-27.1V103.8a27.1 27.1 0 0 0-27.1-27.1z" />
</x:Array>
</local:MyListItem.Buttons>
</local:MyListItem>
@@ -6,6 +6,10 @@ Public Class PageInstanceLeft
''' </summary>
Public Shared Instance As McInstance = Nothing
Private Sub PageInstanceLeft_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
PageSetupUI.HiddenRefresh()
End Sub
Public Sub RefreshModDisabled() Handles Me.Loaded
If Instance IsNot Nothing AndAlso Instance.Modable Then
ItemMod.Visibility = Visibility.Visible
@@ -26,7 +30,7 @@ Public Class PageInstanceLeft
''' <summary>
''' 勾选事件改变页面。
''' </summary>
Private Sub PageCheck(sender As FrameworkElement, e As RouteEventArgs) Handles ItemOverall.Check, ItemMod.Check, ItemModDisabled.Check, ItemSetup.Check, ItemExport.Check
Private Sub PageCheck(sender As FrameworkElement, e As RouteEventArgs) Handles ItemOverall.Check, ItemMod.Check, ItemModDisabled.Check, ItemSetup.Check, ItemModify.Check, ItemExport.Check
'尚未初始化控件属性时,sender.Tag 为 Nothing,会导致切换到页面 0
'若使用 IsLoaded,则会导致模拟点击不被执行(模拟点击切换页面时,控件的 IsLoaded 为 False)
If sender.Tag IsNot Nothing Then PageChange(Val(sender.Tag))
@@ -45,11 +49,14 @@ Public Class PageInstanceLeft
If FrmInstanceModDisabled Is Nothing Then FrmInstanceModDisabled = New PageInstanceModDisabled
Return FrmInstanceModDisabled
Case FormMain.PageSubType.InstanceSetup
If IsNothing(FrmInstanceSetup) Then FrmInstanceSetup = New PageInstanceSetup
If FrmInstanceSetup Is Nothing Then FrmInstanceSetup = New PageInstanceSetup
Return FrmInstanceSetup
Case FormMain.PageSubType.InstanceExport
If FrmInstanceExport Is Nothing Then FrmInstanceExport = New PageInstanceExport
Return FrmInstanceExport
Case FormMain.PageSubType.InstanceModify
If FrmDownloadModify Is Nothing Then FrmDownloadModify = New PageDownloadModify
Return FrmDownloadModify
Case Else
Throw New Exception("未知的版本设置子页面种类:" & ID)
End Select
@@ -60,6 +67,10 @@ Public Class PageInstanceLeft
''' </summary>
Public Sub PageChange(ID As FormMain.PageSubType)
If PageID = ID Then Return
If Instance IsNot Nothing AndAlso McInstanceLock.IsLocked(Instance) Then
Hint("该版本正在被修改,请稍后再切换页面!", HintType.Red)
Return
End If
AniControlEnabled += 1
Try
PageChangeRun(PageGet(ID))
@@ -98,13 +109,20 @@ Public Class PageInstanceLeft
Refresh(FrmMain.PageCurrentSub)
End Sub
Public Sub Refresh(SubType As FormMain.PageSubType)
If Instance IsNot Nothing AndAlso McInstanceLock.IsLocked(Instance) Then
Hint("该版本正在被修改,请稍后再刷新!", HintType.Red)
Return
End If
Select Case SubType
Case FormMain.PageSubType.InstanceMod
PageInstanceMod.Refresh()
ItemMod.Checked = True
Case FormMain.PageSubType.InstanceExport
If FrmInstanceExport IsNot Nothing Then FrmInstanceExport.RefreshAll()
FrmInstanceExport?.RefreshAll()
ItemExport.Checked = True
Case FormMain.PageSubType.InstanceModify
FrmDownloadModify?.PageLoaderRestart()
ItemModify.Checked = True
End Select
End Sub
@@ -284,7 +284,7 @@ Public Class PageInstanceMod
''' 返回输入的文件是否为一个 Mod 文件,仅用于判断拖拽行为。
''' </summary>
Public Shared Function InstallMods(FilePathList As IEnumerable(Of String)) As Boolean
Dim Extension As String = FilePathList.First.AfterLast(".").Lower
Dim Extension As String = PathUtils.GetExtension(FilePathList.First)
'检查文件扩展名
If Not {"jar", "litemod", "disabled", "old"}.Any(Function(t) t = Extension) Then Return False
Logger.Info("文件为 jar/litemod 格式,尝试作为 Mod 安装")
@@ -307,12 +307,15 @@ Public Class PageInstanceMod
Install:
Try
For Each ModFile In FilePathList
Dim NewFileName = PathUtils.GetLastPart(ModFile).Replace(".disabled", "").Replace(".old", "")
Dim NewFileName = (New LocalResourceFile(ModFile)).EnabledName
If Not NewFileName.Contains(".") Then NewFileName += ".jar" '#4227
FileUtils.Copy(ModFile, Instance.PathIndie & "mods\" & NewFileName)
NewFileName = Instance.PathIndie & "mods\" & NewFileName
If PathUtils.IsSame(ModFile, NewFileName) Then Continue For '防止安装相同文件导致文件被删除
FileUtils.Delete(NewFileName, True)
FileUtils.Copy(ModFile, NewFileName)
Next
If FilePathList.IsSingle Then
Hint($"已安装 {PathUtils.GetLastPart(FilePathList.First).Replace(".disabled", "").Replace(".old", "")}!", HintType.Green)
Hint($"已安装 {(New LocalResourceFile(FilePathList.First)).EnabledName}!", HintType.Green)
Else
Hint($"已安装 {FilePathList.Count} 个 Mod!", HintType.Green)
End If
@@ -550,8 +553,8 @@ Install:
Dim File As ResourceVersion = Entry.UpdateFile
If Not File.DownloadAvailable Then Continue For
'确认更新后的文件名
Dim CurrentReplaceName = Entry.ProjectVersion.FileName.Replace(".jar", "").Replace(".old", "").Replace(".disabled", "")
Dim NewestReplaceName = Entry.UpdateFile.FileName.Replace(".jar", "").Replace(".old", "").Replace(".disabled", "")
Dim CurrentReplaceName = PathUtils.GetFileNameWithoutExtension(Entry.ProjectVersion.FileName)
Dim NewestReplaceName = PathUtils.GetFileNameWithoutExtension(Entry.UpdateFile.FileName)
Dim CurrentSegs = CurrentReplaceName.Split("-"c).ToList()
Dim NewestSegs = NewestReplaceName.Split("-"c).ToList()
Dim Shortened As Boolean = False
@@ -62,18 +62,12 @@
</StackPanel>
</local:MyCard>
<local:MyCard Margin="0,0,0,15" Title="快捷方式" x:Name="PanFolder">
<StackPanel Margin="25,40,25,15">
<Grid Margin="0,2,0,7" Height="35">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
</Grid.ColumnDefinitions>
<local:MyButton x:Name="BtnFolderVersion" Text="版本文件夹" MinWidth="140" Padding="13,0" Margin="0,0,20,0" />
<local:MyButton x:Name="BtnFolderSaves" Text="存档文件夹" MinWidth="140" Padding="13,0" Margin="0,0,20,0" Grid.Column="1" />
<local:MyButton x:Name="BtnFolderMods" Text="Mod 文件夹" MinWidth="140" Padding="13,0" Margin="0,0,20,0" Grid.Column="2" />
</Grid>
</StackPanel>
<WrapPanel Margin="25,30,25,20">
<local:MyButton x:Name="BtnFolderVersion" Text="版本文件夹" MinWidth="140" Padding="13,0" Margin="0,13,20,0" Height="35" />
<local:MyButton x:Name="BtnFolderSaves" Text="存档文件夹" MinWidth="140" Padding="13,0" Margin="0,13,20,0" Height="35"/>
<local:MyButton x:Name="BtnFolderMods" Text="Mod 文件夹" MinWidth="140" Padding="13,0" Margin="0,13,20,0" Height="35"/>
<local:MyButton x:Name="BtnFolderScreen" Text="截图文件夹" MinWidth="140" Padding="13,0" Margin="0,13,20,0" Height="35" />
</WrapPanel>
</local:MyCard>
<local:MyCard Margin="0,0,0,15" Title="高级管理" x:Name="PanManage">
<StackPanel Margin="25,40,25,15">
@@ -104,50 +104,21 @@ Public Class PageInstanceOverall
'重命名版本
Private Sub BtnDisplayRename_Click(sender As Object, e As EventArgs) Handles BtnDisplayRename.Click
Try
If McInstanceLock.IsLocked(PageInstanceLeft.Instance) Then
Hint("该版本正在被修改,请稍后再重命名!", HintType.Red)
Return
End If
'确认输入的新名称
Dim OldName As String = PageInstanceLeft.Instance.Name
Dim OldPath As String = PageInstanceLeft.Instance.PathVersion
'修改此部分的同时修改快速安装的版本名检测*
Dim NewName As String = MyMsgBoxInput("重命名版本", "", OldName, New ObjectModel.Collection(Of Validate) From {
New ValidateFolderName(McFolderSelected & "versions", IgnoreCase:=False)})
New ValidateFolderName(McFolderSelected & "versions", IgnoreList:=New List(Of String) From {OldName})
})
If String.IsNullOrWhiteSpace(NewName) Then Return
Dim NewPath As String = McFolderSelected & "versions\" & NewName & "\"
'重新读取版本 JSON 信息,避免 JsonObject 中已被合并的项被重新存储
Dim JsonObject As JObject
Dim OldJsonPath As String = PageInstanceLeft.Instance.GetJsonPath()
Try
JsonObject = FileUtils.ReadAsJson(OldJsonPath)
Catch ex As Exception
Logger.Warn(ex, "在重命名读取 JSON 时失败")
JsonObject = PageInstanceLeft.Instance.JsonObject
End Try
'重命名主文件夹
DirectoryUtils.Move(OldPath, NewPath)
'清理 ini 缓存
PageInstanceLeft.Instance.ResetSettingsCache()
'重命名 jar 文件与 natives 文件夹
'不能进行遍历重命名,否则在版本名很短的时候容易误伤其他文件(#6443)
If DirectoryUtils.Exists($"{NewPath}{OldName}-natives") Then DirectoryUtils.Move($"{NewPath}{OldName}-natives", $"{NewPath}{NewName}-natives")
If FileUtils.Exists($"{NewPath}{OldName}.jar") Then FileUtils.Move($"{NewPath}{OldName}.jar", $"{NewPath}{NewName}.jar")
'替换版本设置文件中的路径
If FileUtils.Exists(NewPath & "PCL\Setup.ini") Then
FileUtils.Write(NewPath & "PCL\Setup.ini", FileUtils.ReadAsString(NewPath & "PCL\Setup.ini").Replace(OldPath, NewPath))
End If
'更改已选中的版本
If ReadIni(McFolderSelected & "PCL.ini", "Version") = OldName Then
WriteIni(McFolderSelected & "PCL.ini", "Version", NewName)
End If
'更新版本 Json
Try
JsonObject("id") = NewName
FileUtils.Delete(NewPath & PathUtils.GetLastPart(OldJsonPath))
FileUtils.Write(NewPath & NewName & ".json", JsonObject.ToString)
Catch ex As Exception
Logger.Warn(ex, "重命名版本 json 失败")
End Try
Dim NewInstance = PageInstanceLeft.Instance.Rename(NewName)
'刷新与提示
Hint("重命名成功!", HintType.Green)
PageInstanceLeft.Instance = New McInstance(NewName).Load()
PageInstanceLeft.Instance = NewInstance
If Not IsNothing(McInstanceSelected) AndAlso McInstanceSelected.Equals(PageInstanceLeft.Instance) Then WriteIni(McFolderSelected & "PCL.ini", "Version", NewName)
Reload()
LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")
@@ -157,7 +128,7 @@ Public Class PageInstanceOverall
End Sub
'版本图标
Private Sub ComboDisplayLogo_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles ComboDisplayLogo.SelectionChanged
Private Sub ComboDisplayLogo_SelectionChanged() Handles ItemDisplayLogoCustom.PreviewMouseLeftButtonUp, ComboDisplayLogo.SelectionChanged
If Not (IsLoad AndAlso AniControlEnabled = 0) Then Return
'选择 自定义 时修改图片
Try
@@ -228,6 +199,13 @@ Public Class PageInstanceOverall
OpenExplorer(Folder)
End Sub
'截图文件夹
Private Sub BtnFolderScreen_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnFolderScreen.Click
Dim Folder As String = PageInstanceLeft.Instance.PathIndie & "screenshots\"
DirectoryUtils.Create(Folder)
OpenExplorer(Folder)
End Sub
#End Region
#Region "卡片:管理"
@@ -259,6 +237,10 @@ Public Class PageInstanceOverall
'补全文件
Private Sub BtnManageCheck_Click(sender As Object, e As EventArgs) Handles BtnManageCheck.Click
Try
If McInstanceLock.IsLocked(PageInstanceLeft.Instance) Then
Hint("该版本正在被修改,请稍后再补全文件!", HintType.Red)
Return
End If
'忽略文件检查提示
If ShouldIgnoreFileCheck(PageInstanceLeft.Instance) Then
Hint("请先关闭 [版本设置 → 设置 → 高级启动选项 → 关闭文件校验],然后再尝试补全文件!", HintType.Blue)
@@ -296,10 +278,17 @@ Public Class PageInstanceOverall
'修改此代码时,同时修改 PageSelectRight 中的代码
Private Sub BtnManageDelete_Click(sender As Object, e As EventArgs) Handles BtnManageDelete.Click
Try
If McInstanceLock.IsLocked(PageInstanceLeft.Instance) Then
Hint("该版本正在被修改,请稍后再删除!", HintType.Red)
Return
End If
Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown
Dim IsHintIndie As Boolean = PageInstanceLeft.Instance.State <> McInstanceState.Error AndAlso PageInstanceLeft.Instance.PathIndie <> McFolderSelected
Dim IsHintIndie As Boolean = PageInstanceLeft.Instance.State <> McInstance.McInstanceState.Error AndAlso PageInstanceLeft.Instance.PathIndie <> McFolderSelected
Dim SavesFolder = DirectoryUtils.GetInfo(PageInstanceLeft.Instance.PathIndie & "saves\")
Dim SaveEntries = If(IsHintIndie AndAlso SavesFolder.Exists, SavesFolder.EnumerateDirectories.OrderByDescending(Function(Save) Save.LastWriteTime).Select(Function(Save) $"{Save.Name}(上次修改:{StringUtils.FormatTimeSpan(Save.LastWriteTime - Date.Now, False)})").ToList, New List(Of String))
Select Case MyMsgBox($"你确定要{If(IsShiftPressed, "永久", "")}删除版本 {PageInstanceLeft.Instance.Name} 吗?" &
If(IsHintIndie, vbCrLf & "由于该版本开启了版本隔离,删除版本时该版本对应的存档、资源包、Mod 等文件也将被一并删除!", ""),
If(IsHintIndie, vbCrLf & "该版本对应的存档、资源包、Mod 等文件也将被一并删除!" &
If(SaveEntries.Any, vbCrLf & vbCrLf & "这会删除以下存档:" & vbCrLf & "· " & SaveEntries.Join(vbCrLf & "· "), ""), ""),
"版本删除确认", , "取消",, IsHintIndie OrElse IsShiftPressed)
Case 1
PageInstanceLeft.Instance.ResetSettingsCache()
@@ -83,7 +83,7 @@ Public Class PageInstanceSetup
Settings.Reset("VersionServerLogin", Instance:=PageInstanceLeft.Instance)
Settings.Reset("VersionArgumentIndieV2", Instance:=PageInstanceLeft.Instance)
Settings.Reset("VersionAdvanceAssets", Instance:=PageInstanceLeft.Instance)
Configs.InstanceForcedJava.Reset(PageInstanceLeft.Instance.Config)
Configs.JavaForced.Reset(PageInstanceLeft.Instance.Config)
JavaListRefreshWorker.Start()
Logger.Info("已初始化版本独立设置")
@@ -474,7 +474,7 @@ PreFin:
'========================================== 显示结果 ==========================================
BtnArgumentJavaSearch.IsEnabled = True
ComboArgumentJavaSelect.IsEnabled = True
Dim SelectedJava As Java = Configs.InstanceForcedJava.Get(PageInstanceLeft.Instance.Config)
Dim SelectedJava As Java = Configs.JavaForced.Get(PageInstanceLeft.Instance.Config)
'更新下拉框文本
If Not Configs.JavaList.Get().Any() Then
LabArgumentJava.Text = "未找到 Java,点击以导入已有的 Java"
@@ -492,14 +492,14 @@ PreFin:
AddHandler JavaItem.MouseLeftButtonUp, Sub(sender As Object, e As MouseButtonEventArgs) e.Handled = True
AddHandler JavaItem.Click,
Sub()
Configs.InstanceForcedJava.Set(JavaEntry, PageInstanceLeft.Instance.Config)
Configs.JavaForced.Set(JavaEntry, PageInstanceLeft.Instance.Config)
UpdateJavaList()
ComboArgumentJavaSelect.IsDropDownOpen = False
End Sub
ComboArgumentJavaSelect.Items.Add(JavaItem)
Dim Buttons As New List(Of MyIconButton)
'从列表中移除按钮
Dim IsOfficial As Boolean = JavaEntry.Folder.StartsWithF($"{Paths.AppData}.minecraft\runtime\")
Dim IsOfficial As Boolean = PathUtils.IsParentOf($"{Paths.AppData}.minecraft\runtime\", JavaEntry.Folder)
Dim DeleteButton As New MyIconButton With {.Logo = Logo.IconButtonStop, .Height = 24, .Width = 24}
DeleteButton.IsEnabled = Not IsOfficial
DeleteButton.ToolTip = If(DeleteButton.IsEnabled, "从列表中移除", "无法移除官方 Java")
@@ -245,7 +245,8 @@ Retry:
{"Cherry Blossom", "樱花披风"}, {"15th Anniversary", "15 周年纪念披风"}, {"Purple Heart", "紫色心形披风"},
{"Follower's", "追随者披风"}, {"MCC 15th Year", "MCC 15 周年披风"}, {"Minecraft Experience", "村民救援披风"},
{"Mojang Office", "Mojang 办公室披风"}, {"Home", "家园披风"}, {"Menace", "入侵披风"}, {"Yearn", "渴望披风"},
{"Common", "普通披风"}, {"Pan", "薄煎饼披风"}, {"Founder's", "创始人披风"}, {"Copper", "铜披风"}, {"Zombie Horse", "僵尸马披风"}, {"Builder", "建造者披风"}, {"Crafter", "工匠披风"}
{"Common", "普通披风"}, {"Pan", "薄煎饼披风"}, {"Founder's", "创始人披风"}, {"Copper", "铜披风"}, {"Zombie Horse", "僵尸马披风"}, {"Builder", "建造者披风"}, {"Crafter", "工匠披风"},
{"Aurora", "极光披风"}
}
Dim SelectionControl As New List(Of IMyRadio) From {
New MyRadioBox With {.Text = "无披风", .Checked = Not SkinData("capes").Any(Function(c) c("state")?.ToString = "ACTIVE")}}
@@ -65,7 +65,7 @@ Public Class PageLaunchLeft
'确认 Minecraft 版本存在
Dim Selection As String = ReadIni(McFolderSelected & "PCL.ini", "Version")
Dim Instance As McInstance = If(Selection = "", Nothing, New McInstance(Selection))
If Instance Is Nothing OrElse Not Instance.PathVersion.StartsWithF(McFolderSelected) OrElse Not Instance.Check() Then
If Instance Is Nothing OrElse Not PathUtils.IsParentOf(McFolderSelected, Instance.PathVersion) OrElse Not Instance.Check() Then
'无效的版本
Logger.Info($"当前选择的 Minecraft 版本无效:{If(Instance Is Nothing, "null", Instance.PathVersion)}", If(IsNothing(Instance), LogBehavior.None, LogBehavior.ToastIfDebug))
If Not McInstanceListLoader.State = LoadState.Finished Then LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\", WaitForExit:=True)
@@ -691,7 +691,7 @@ ExitRefresh:
End If
End Sub
'取消按钮
Private Sub BtnCancel_Click() Handles BtnCancel.Click
Public Sub BtnCancel_Click() Handles BtnCancel.Click
If McLaunchLoaderReal IsNot Nothing Then
McLaunchLoaderReal.Cancel()
McLaunchLog("已取消启动")
@@ -709,6 +709,10 @@ ExitRefresh:
'版本设置按钮
Private Sub BtnMore_Click() Handles BtnMore.Click
If McLaunchLoader.State = LoadState.Loading Then Return
If McInstanceLock.IsLocked(McInstanceSelected) Then
Hint("该版本正在被修改,暂时无法进入版本设置!", HintType.Red)
Return
End If
McInstanceSelected.Load()
PageInstanceLeft.Instance = McInstanceSelected
FrmMain.PageChange(FormMain.PageType.InstanceSetup, 0)
@@ -3,6 +3,7 @@
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:behaviors="clr-namespace:PCL.Controls.Behaviors"
xmlns:local="clr-namespace:PCL" PanScroll="PanBack">
<local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
<StackPanel Name="PanMain" Margin="25,25,25,10" Grid.IsSharedSizeScope="True">
@@ -11,7 +12,9 @@
<StackPanel x:Name="PanCustom">
</StackPanel>
<local:MyCard Margin="0,0,0,15" Title="启动日志" x:Name="PanLog">
<TextBlock x:Name="LabLog" Margin="20,38,20,18" TextTrimming="None" TextWrapping="Wrap" />
<local:MyTextBox x:Name="LabLog" Margin="15,34,15,13" Padding="0,4" IsReadOnly="True" IsUndoEnabled="False"
AcceptsReturn="True" TextWrapping="Wrap" BorderThickness="0" HasBackground="False"
VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled" />
</local:MyCard>
</StackPanel>
</local:MyScrollViewer>
@@ -89,7 +89,7 @@ Public Class PageLaunchRight
Url = "https://files.mcimirror.top/PCL"
Case 11
Logger.Info("主页预设:杂志主页")
Url = "http://118.195.192.193:26995/d/magazine-homepage-pcl/Custom.xaml"
Url = "https://gh-proxy.com/https://github.com/Neclyon/Magazine-Homepage-PCL/raw/main/output/Custom.xaml"
Case 12
Logger.Info("主页预设:PCL GitHub 仪表盘")
Url = "https://ddf.pcl-community.top/Custom.xaml"
@@ -101,17 +101,40 @@ Public Class PageLaunchRight
Url = "https://pcl.wyc-w.top/index.xaml"
Case 15
Logger.Info("主页预设:Minecraft 芝士站")
Url = "https://www.xxag.top/mkss"
Url = "https://mkss-services.awaeric.xyz"
Case 16
Logger.Info("主页预设:整合包推荐引擎")
Url = "https://qawsedrftgyhujiko.fun/pcl2/Custom.xaml"
Case 17
Logger.Info("Bangumi 番剧主页")
Url = "https://bangumi.p.kaphia.qzz.io"
Logger.Info("主页预设:Bangumi 番剧主页")
Url = "https://bangumi.p.kaphia.top"
Case 18
Logger.Info("主页预设:Bilibili 热门")
Url = "https://bilibili.p.kaphia.top"
Case 19
Logger.Info("主页预设:Music 云音乐热门")
Url = "https://cloudmusic.p.kaphia.top"
Case 20
Logger.Info("主页预设:模组推荐")
Url = "https://g-fish.dpdns.org/download/ModPage.xaml"
Case 21
Logger.Info("主页预设:每日游戏推荐")
Url = "https://pcl.xn--9iq209asnop94b.top/download/GamePage.xaml"
Case 22
Logger.Info("主页预设:Wallpaper 每日壁纸")
Url = "https://wallpaper.p.kaphia.top"
Case 23
Logger.Info("主页预设:Weather 天气预报")
Url = "https://weather-p.kaphia.top/{variable:PCLDailyWeatherPage:choose}.xaml"
Case 24
Logger.Info("主页预设:新一代狗屁王大杂烩主页")
Url = "https://end.g-fish.dpdns.org/Custom.xaml"
End Select
End Select
'联网下载
If Not String.IsNullOrWhiteSpace(Url) Then
'替换自定义变量与设置
Url = ArgumentReplace(Url, AddressOf WebUtility.HtmlEncode)
If Url = Settings.Get(Of String)("CacheSavedPageUrl") AndAlso FileUtils.Exists(PathTemp & "Cache\Custom.xaml") Then
'缓存可用
Logger.Info("主页自定义数据来源:联网缓存文件")
@@ -139,8 +162,6 @@ Public Class PageLaunchRight
Dim Address As String = Task.Input.Address '#3721 中连续触发两次导致内容变化
Dim ShouldRefresh As Boolean = Task.Input.ShouldRefresh
Try
'替换自定义变量与设置
Address = ArgumentReplace(Address, AddressOf WebUtility.HtmlEncode)
'获取版本校验地址
Dim VersionAddress As String
If Address.Contains(".xaml") Then
@@ -21,7 +21,7 @@
<TextBlock Name="TextEmail" Text="加载中" Margin="8,4,8,0" FontSize="12" HorizontalAlignment="Center" TextTrimming="CharacterEllipsis" Foreground="{DynamicResource ColorBrushGray3}" />
</StackPanel>
<local:MyIconButton ToolTip="更换角色" Grid.Row="1" Height="14" Opacity="0" x:Name="BtnEdit" ToolTipService.InitialShowDelay="50" Grid.Column="0" HorizontalAlignment="Center" Logo="F1 M 53.2929,21.2929L 54.7071,22.7071C 56.4645,24.4645 56.4645,27.3137 54.7071,29.0711L 52.2323,31.5459L 44.4541,23.7677L 46.9289,21.2929C 48.6863,19.5355 51.5355,19.5355 53.2929,21.2929 Z M 31.7262,52.052L 23.948,44.2738L 43.0399,25.182L 50.818,32.9601L 31.7262,52.052 Z M 23.2409,47.1023L 28.8977,52.7591L 21.0463,54.9537L 23.2409,47.1023 Z" Margin="8,0,0,0" VerticalAlignment="Center" />
<local:MyIconButton ToolTip="退出登录" Grid.Row="1" Height="14" Opacity="0" x:Name="BtnExit" ToolTipService.InitialShowDelay="50" Grid.Column="2" HorizontalAlignment="Center" Logo="F1 M 51.0071,19.0027L 51.0071,27.0038L 48.0067,27.0038L 48.0067,22.0031L 25.0035,22.0031L 25.0035,54.0075L 48.0067,54.0075L 48.0067,49.0068L 51.0071,49.0068L 51.0071,57.008L 22.1698,57.008L 22.0031,19.0027L 51.0071,19.0027 Z M 30.0042,35.0049L 45.2563,35.0049L 37.005,28.0039L 47.0066,28.0039L 58.5082,38.0053L 47.0066,48.0067L 37.005,48.0067L 45.2563,41.0057L 30.0042,41.0057L 30.0042,35.0049 Z" Margin="0,0,8,0" VerticalAlignment="Center" />
<local:MyIconButton ToolTip="切换账号" Grid.Row="1" Height="14" Opacity="0" x:Name="BtnExit" ToolTipService.InitialShowDelay="50" Grid.Column="2" HorizontalAlignment="Center" Logo="M512 0A512 512 0 1 0 1024 512 512 512 0 0 0 512 0z m0 941.407086A429.407086 429.407086 0 1 1 941.407086 512 429.407086 429.407086 0 0 1 512 941.407086z m235.139657-368.64H264.045714a43.505371 43.505371 0 0 0-31.246628 72.060343l159.305143 166.765714a41.281829 41.281829 0 1 0 59.626057-56.788114l-94.997943-99.474286H747.227429a41.311086 41.311086 0 1 0 0-82.622172zM262.670629 450.940343H745.764571a43.505371 43.505371 0 0 0 31.246629-72.352914l-159.305143-166.765715a41.281829 41.281829 0 1 0-59.713828 57.022172l94.968685 99.474285H262.670629a41.281829 41.281829 0 0 0 0 82.563658z" Margin="0,0,8,0" VerticalAlignment="Center" />
</Grid>
<local:MySkin HorizontalAlignment="Center" VerticalAlignment="Top" x:Name="Skin" HasCape="False" />
</Grid>
@@ -45,7 +45,7 @@ Public Class PageLoginLegacy
If LoginData.UserName.Trim = "" Then Return "玩家名不能为空!"
If LoginData.UserName.Contains("""") Then Return "玩家名不能包含英文引号!"
If McInstanceSelected IsNot Nothing AndAlso
McInstanceSelected.Version.Vaild AndAlso McInstanceSelected.Version.Vanilla >= New Version(20, 0, 3) AndAlso
McInstanceSelected.Version.Valid AndAlso McInstanceSelected.Version.Vanilla >= New Version(20, 0, 3) AndAlso
LoginData.UserName.Trim.Length > 16 Then
Return "自 1.20.3 起,玩家名至多只能包含 16 个字符!"
End If
@@ -18,7 +18,7 @@ Public Class PageLoginMs
ComboAccounts.Items.Add(Item)
Next
Catch ex As Exception
Logger.Error(ex, $"微软登录信息出错,登录信息已被重置({Settings.Get(Of String)("LoginMsJson")})", LogBehavior.Toast)
Logger.Error(ex, $"微软登录信息出错,登录信息已被重置", LogBehavior.Toast)
Settings.Set("LoginMsJson", "{}")
End Try
'如果不保留输入,刷新列表后自动选择第一项
@@ -28,6 +28,8 @@
<Separator />
<local:MyMenuItem Click="BtnSkinCape_Click" Header="修改披风"/>
<local:MyMenuItem Click="BtnSkinCapeRefresh_Click" Header="刷新披风列表"/>
<local:MyMenuItem Header="使用 CDKEY 兑换奖励"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.minecraft.net/zh-hans/redeem" />
</ContextMenu>
</local:MyIconButton.ContextMenu>
</local:MyIconButton>
@@ -36,8 +38,10 @@
LogoScale="1.1" Logo="M462.336 924.891429L73.142857 950.857143l25.965714-389.193143 467.017143-467.017143a73.398857 73.398857 0 0 1 103.789715 0l259.437714 259.437714a73.398857 73.398857 0 0 1 0 103.789715l-467.017143 467.017143z m155.684571-778.349715L202.861714 561.664l259.474286 259.474286L877.458286 405.942857 618.057143 146.541714zM151.003429 873.033143l233.508571-25.965714-207.579429-207.579429-25.965714 233.545143z m544.841142-492.982857l51.894858 51.894857-233.508572 233.508571-51.931428-51.894857 233.545142-233.508571z">
<local:MyIconButton.ContextMenu>
<ContextMenu Closed="HidePanel" HorizontalOffset="10" VerticalOffset="18">
<local:MyMenuItem Header="修改密码" local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://account.live.com/password/Change" />
<local:MyMenuItem Header="修改玩家名" local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.minecraft.net/zh-hans/msaprofile/mygames/editprofile" />
<local:MyMenuItem Header="修改密码"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://account.live.com/password/Change" />
<local:MyMenuItem Header="修改玩家名"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.minecraft.net/zh-hans/msaprofile/mygames/editprofile" />
</ContextMenu>
</local:MyIconButton.ContextMenu>
</local:MyIconButton>
@@ -22,7 +22,7 @@
</StackPanel>
<local:MyIconButton ToolTip="更改密码" Grid.Row="1" Height="14" Opacity="0" x:Name="BtnEdit" ToolTipService.InitialShowDelay="50" Grid.Column="0" HorizontalAlignment="Center" Logo="F1 M 53.2929,21.2929L 54.7071,22.7071C 56.4645,24.4645 56.4645,27.3137 54.7071,29.0711L 52.2323,31.5459L 44.4541,23.7677L 46.9289,21.2929C 48.6863,19.5355 51.5355,19.5355 53.2929,21.2929 Z M 31.7262,52.052L 23.948,44.2738L 43.0399,25.182L 50.818,32.9601L 31.7262,52.052 Z M 23.2409,47.1023L 28.8977,52.7591L 21.0463,54.9537L 23.2409,47.1023 Z" Margin="8,0,0,0" VerticalAlignment="Center"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://login.mc-user.com:233/account/changepw" />
<local:MyIconButton ToolTip="退出登录" Grid.Row="1" Height="14" Opacity="0" x:Name="BtnExit" ToolTipService.InitialShowDelay="50" Grid.Column="2" HorizontalAlignment="Center" Logo="F1 M 51.0071,19.0027L 51.0071,27.0038L 48.0067,27.0038L 48.0067,22.0031L 25.0035,22.0031L 25.0035,54.0075L 48.0067,54.0075L 48.0067,49.0068L 51.0071,49.0068L 51.0071,57.008L 22.1698,57.008L 22.0031,19.0027L 51.0071,19.0027 Z M 30.0042,35.0049L 45.2563,35.0049L 37.005,28.0039L 47.0066,28.0039L 58.5082,38.0053L 47.0066,48.0067L 37.005,48.0067L 45.2563,41.0057L 30.0042,41.0057L 30.0042,35.0049 Z" Margin="0,0,8,0" VerticalAlignment="Center" />
<local:MyIconButton ToolTip="切换账号" Grid.Row="1" Height="14" Opacity="0" x:Name="BtnExit" ToolTipService.InitialShowDelay="50" Grid.Column="2" HorizontalAlignment="Center" Logo="M512 0A512 512 0 1 0 1024 512 512 512 0 0 0 512 0z m0 941.407086A429.407086 429.407086 0 1 1 941.407086 512 429.407086 429.407086 0 0 1 512 941.407086z m235.139657-368.64H264.045714a43.505371 43.505371 0 0 0-31.246628 72.060343l159.305143 166.765714a41.281829 41.281829 0 1 0 59.626057-56.788114l-94.997943-99.474286H747.227429a41.311086 41.311086 0 1 0 0-82.622172zM262.670629 450.940343H745.764571a43.505371 43.505371 0 0 0 31.246629-72.352914l-159.305143-166.765715a41.281829 41.281829 0 1 0-59.713828 57.022172l94.968685 99.474285H262.670629a41.281829 41.281829 0 0 0 0 82.563658z" Margin="0,0,8,0" VerticalAlignment="Center" />
</Grid>
<local:MySkin HorizontalAlignment="Center" VerticalAlignment="Top" x:Name="Skin" HasCape="False" />
</Grid>
@@ -487,8 +487,8 @@ Public Class PageLinkMain
If FileUtils.Exists(PathEasyTier & "EasyTier.zip") Then
'解压
Dim ExtractPath As String = RequestTaskTempFolder()
FileUtils.ExtractToDirectory(PathEasyTier & "EasyTier.zip", ExtractPath,
progressHandler:=Sub(Percentage) Task.Progress = Percentage * 0.05)
'FileUtils.ExtractToDirectory(PathEasyTier & "EasyTier.zip", ExtractPath,
' progressHandler:=Sub(Percentage) Task.Progress = Percentage * 0.05)
DirectoryUtils.Copy(DirectoryUtils.EnumerateDirectories(ExtractPath, True).First, PathEasyTier)
'重命名
FileUtils.Move(PathEasyTier & "easytier-core.exe", PathEasyTier & "联机模块.exe")
@@ -11,6 +11,7 @@
<Grid.RowDefinitions>
<RowDefinition Height="45" />
<RowDefinition Height="45" />
<RowDefinition Height="45" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
@@ -18,21 +19,27 @@
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<local:MyListItem IsHitTestVisible="False" Title="龙腾猫跃" Margin="-5,0,15,0" Info="Plain Craft Launcher 的开发者!" Grid.Row="0" Grid.Column="1" />
<local:MyListItem IsHitTestVisible="False" Title="Plain Craft Launcher 2" Margin="-5,0,15,0" Info="当前版本:%VERSION%(%VERSIONCODE%.%BRANCH%)" Grid.Row="1" Grid.Column="1" x:Name="ItemAboutPcl" />
<Image Source="/Plain Craft Launcher 2;component/Images/Heads/Logo.png" Grid.Row="1" Margin="3">
<local:MyListItem IsHitTestVisible="False" Title="HerobrineXia" Margin="-5,0,15,0" Info="Plain Craft Launcher 的协力开发者!" Grid.Row="1" Grid.Column="1" />
<local:MyListItem IsHitTestVisible="False" Title="Plain Craft Launcher 2" Margin="-5,0,15,0" Info="当前版本:%VERSION%(%VERSIONCODE%.%BRANCH%)" Grid.Row="2" Grid.Column="1" x:Name="ItemAboutPcl" />
<Image Source="/Plain Craft Launcher 2;component/Images/Heads/LTCat.jpg" Grid.Row="0" Margin="3">
<Image.Clip>
<EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
</Image.Clip>
</Image>
<Image Source="/Plain Craft Launcher 2;component/Images/Heads/LTCat.jpg" Grid.Row="0" Margin="3">
<Image Source="/Plain Craft Launcher 2;component/Images/Heads/HerobrineXia.png" Grid.Row="1" Margin="3">
<Image.Clip>
<EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
</Image.Clip>
</Image>
<Image Source="/Plain Craft Launcher 2;component/Images/Heads/Logo.png" Grid.Row="2" Margin="3">
<Image.Clip>
<EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
</Image.Clip>
</Image>
<local:MyButton Text="赞助 PCL !" Grid.Column="2" Grid.Row="0" Height="35" ColorType="Highlight"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://meloong.com/afd/a/LTCat" />
<local:MyButton Text="查看源代码" Grid.Column="2" Grid.Row="1" Height="35"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/Hex-Dragon/PCL2" />
<local:MyButton Text="检查更新" Grid.Column="2" Grid.Row="2" Height="35"
x:Name="BtnAboutUpdate" />
</Grid>
</local:MyCard>
<local:MyCard Margin="0,0,0,15" Title="特别鸣谢">
@@ -124,335 +131,391 @@
<local:MyCard Margin="0,0,0,15" Title="赞助者">
<StackPanel Margin="25,40,19,20">
<TextBlock HorizontalAlignment="Left" Text="以下是近期的高级赞助者们!感谢大家对 PCL 的支持!!!" Margin="0,0,25,5.5" TextTrimming="None" TextWrapping="WrapWithOverflow" />
<WrapPanel Margin="0,0,0,10"> <TextBlock Margin="0,1.5,6,0" Text="." />
<TextBlock Margin="0,1.5,6,0" Text="7" />
<TextBlock Margin="0,1.5,6,0" Text="ln" />
<TextBlock Margin="0,1.5,6,0" Text="sc" />
<TextBlock Margin="0,1.5,6,0" Text="龙" />
<TextBlock Margin="0,1.5,6,0" Text="人" />
<TextBlock Margin="0,1.5,6,0" Text="溈" />
<TextBlock Margin="0,1.5,6,0" Text="域" />
<TextBlock Margin="0,1.5,6,0" Text="菌" />
<TextBlock Margin="0,1.5,6,0" Text="。" />
<TextBlock Margin="0,1.5,6,0" Text="東" />
<TextBlock Margin="0,1.5,6,0" Text="喆" />
<TextBlock Margin="0,1.5,6,0" Text="辞" />
<TextBlock Margin="0,1.5,6,0" Text="溯" />
<TextBlock Margin="0,1.5,6,0" Text="乔" />
<TextBlock Margin="0,1.5,6,0" Text="liy" />
<TextBlock Margin="0,1.5,6,0" Text="xu" />
<TextBlock Margin="0,1.5,6,0" Text="lor" />
<TextBlock Margin="0,1.5,6,0" Text="💢" />
<TextBlock Margin="0,1.5,6,0" Text="ikii" />
<TextBlock Margin="0,1.5,6,0" Text="ym" />
<TextBlock Margin="0,1.5,6,0" Text="tstt" />
<TextBlock Margin="0,1.5,6,0" Text="hjk" />
<TextBlock Margin="0,1.5,6,0" Text="s11" />
<TextBlock Margin="0,1.5,6,0" Text="gxx" />
<TextBlock Margin="0,1.5,6,0" Text="Lhy" />
<TextBlock Margin="0,1.5,6,0" Text="jojo" />
<TextBlock Margin="0,1.5,6,0" Text="沙轻" />
<TextBlock Margin="0,1.5,6,0" Text="阳阳" />
<TextBlock Margin="0,1.5,6,0" Text="画卿" />
<TextBlock Margin="0,1.5,6,0" Text="幽灵" />
<TextBlock Margin="0,1.5,6,0" Text="御衡" />
<TextBlock Margin="0,1.5,6,0" Text="语墨" />
<TextBlock Margin="0,1.5,6,0" Text="若希" />
<TextBlock Margin="0,1.5,6,0" Text="少帅" />
<TextBlock Margin="0,1.5,6,0" Text="小怂" />
<TextBlock Margin="0,1.5,6,0" Text="叶虚" />
<TextBlock Margin="0,1.5,6,0" Text="伍十" />
<TextBlock Margin="0,1.5,6,0" Text="计计" />
<TextBlock Margin="0,1.5,6,0" Text="雪雪" />
<TextBlock Margin="0,1.5,6,0" Text="李镜" />
<TextBlock Margin="0,1.5,6,0" Text="迪勒" />
<TextBlock Margin="0,1.5,6,0" Text="。。" />
<TextBlock Margin="0,1.5,6,0" Text="优优" />
<TextBlock Margin="0,1.5,6,0" Text="青烬" />
<TextBlock Margin="0,1.5,6,0" Text="苏籁" />
<TextBlock Margin="0,1.5,6,0" Text="阿江" />
<TextBlock Margin="0,1.5,6,0" Text="绵绵" />
<TextBlock Margin="0,1.5,6,0" Text="萧白" />
<TextBlock Margin="0,1.5,6,0" Text="枫灵" />
<TextBlock Margin="0,1.5,6,0" Text="豆腐" />
<TextBlock Margin="0,1.5,6,0" Text="无聊" />
<TextBlock Margin="0,1.5,6,0" Text="闻序" />
<TextBlock Margin="0,1.5,6,0" Text="风鸢" />
<TextBlock Margin="0,1.5,6,0" Text="殉情" />
<TextBlock Margin="0,1.5,6,0" Text="朱磊" />
<TextBlock Margin="0,1.5,6,0" Text="先森" />
<TextBlock Margin="0,1.5,6,0" Text="文博" />
<TextBlock Margin="0,1.5,6,0" Text="月白" />
<TextBlock Margin="0,1.5,6,0" Text="角慕" />
<TextBlock Margin="0,1.5,6,0" Text="楽天" />
<TextBlock Margin="0,1.5,6,0" Text="九杳" />
<TextBlock Margin="0,1.5,6,0" Text="昨易" />
<TextBlock Margin="0,1.5,6,0" Text="W.H" />
<TextBlock Margin="0,1.5,6,0" Text="嘟嘟" />
<TextBlock Margin="0,1.5,6,0" Text="断疆" />
<TextBlock Margin="0,1.5,6,0" Text="麋鹿" />
<TextBlock Margin="0,1.5,6,0" Text="云心" />
<TextBlock Margin="0,1.5,6,0" Text="睚眦" />
<TextBlock Margin="0,1.5,6,0" Text="诡奇" />
<TextBlock Margin="0,1.5,6,0" Text="咲狐" />
<TextBlock Margin="0,1.5,6,0" Text="白云" />
<TextBlock Margin="0,1.5,6,0" Text="泽闻" />
<TextBlock Margin="0,1.5,6,0" Text="逍遥" />
<TextBlock Margin="0,1.5,6,0" Text="木鱼" />
<TextBlock Margin="0,1.5,6,0" Text="凌月" />
<TextBlock Margin="0,1.5,6,0" Text="梓昕" />
<TextBlock Margin="0,1.5,6,0" Text="白菜" />
<TextBlock Margin="0,1.5,6,0" Text="云上" />
<TextBlock Margin="0,1.5,6,0" Text="老赵" />
<TextBlock Margin="0,1.5,6,0" Text="拾岳" />
<TextBlock Margin="0,1.5,6,0" Text="济川" />
<TextBlock Margin="0,1.5,6,0" Text="樾安" />
<TextBlock Margin="0,1.5,6,0" Text="蜜狳" />
<TextBlock Margin="0,1.5,6,0" Text="七月" />
<TextBlock Margin="0,1.5,6,0" Text="北瓜" />
<TextBlock Margin="0,1.5,6,0" Text="昔涟" />
<TextBlock Margin="0,1.5,6,0" Text="浅璃" />
<TextBlock Margin="0,1.5,6,0" Text="成盏" />
<TextBlock Margin="0,1.5,6,0" Text="而后" />
<TextBlock Margin="0,1.5,6,0" Text="南安" />
<TextBlock Margin="0,1.5,6,0" Text="ryan" />
<TextBlock Margin="0,1.5,6,0" Text="xx_α" />
<TextBlock Margin="0,1.5,6,0" Text="欸·柚" />
<TextBlock Margin="0,1.5,6,0" Text="liutd" />
<TextBlock Margin="0,1.5,6,0" Text="KIO3" />
<TextBlock Margin="0,1.5,6,0" Text=" 无悔" />
<TextBlock Margin="0,1.5,6,0" Text="4440" />
<TextBlock Margin="0,1.5,6,0" Text="Dr.松" />
<TextBlock Margin="0,1.5,6,0" Text="LiMu" />
<TextBlock Margin="0,1.5,6,0" Text="KING" />
<TextBlock Margin="0,1.5,6,0" Text="ᝰᧁꫀꪑⅈꪀⅈ💫" />
<TextBlock Margin="0,1.5,6,0" Text="ming" />
<TextBlock Margin="0,1.5,6,0" Text="CIYE_" />
<TextBlock Margin="0,1.5,6,0" Text="祇–仆" />
<TextBlock Margin="0,1.5,6,0" Text="Pluto" />
<TextBlock Margin="0,1.5,6,0" Text="LYX_7" />
<TextBlock Margin="0,1.5,6,0" Text="刘666" />
<TextBlock Margin="0,1.5,6,0" Text="Dong" />
<TextBlock Margin="0,1.5,6,0" Text="Lecen" />
<TextBlock Margin="0,1.5,6,0" Text="Hikari" />
<TextBlock Margin="0,1.5,6,0" Text="Vertin" />
<TextBlock Margin="0,1.5,6,0" Text="35035" />
<TextBlock Margin="0,1.5,6,0" Text="Dye乄" />
<TextBlock Margin="0,1.5,6,0" Text="白椰菜" />
<TextBlock Margin="0,1.5,6,0" Text="小白龙" />
<TextBlock Margin="0,1.5,6,0" Text="TeRiRi" />
<TextBlock Margin="0,1.5,6,0" Text="索菲亚" />
<TextBlock Margin="0,1.5,6,0" Text="穷哥们" />
<TextBlock Margin="0,1.5,6,0" Text="华华子" />
<TextBlock Margin="0,1.5,6,0" Text="欢喜果" />
<TextBlock Margin="0,1.5,6,0" Text="大涡流" />
<TextBlock Margin="0,1.5,6,0" Text="无名氏" />
<TextBlock Margin="0,1.5,6,0" Text="power" />
<TextBlock Margin="0,1.5,6,0" Text="苏清绾" />
<TextBlock Margin="0,1.5,6,0" Text="四月初" />
<TextBlock Margin="0,1.5,6,0" Text="雪落骆" />
<TextBlock Margin="0,1.5,6,0" Text="秋天爽" />
<TextBlock Margin="0,1.5,6,0" Text="茏三颜" />
<TextBlock Margin="0,1.5,6,0" Text="月落蝶" />
<TextBlock Margin="0,1.5,6,0" Text="蒲咩咩" />
<TextBlock Margin="0,1.5,6,0" Text="糖不哭" />
<TextBlock Margin="0,1.5,6,0" Text="风雨颜" />
<TextBlock Margin="0,1.5,6,0" Text="花江湖" />
<TextBlock Margin="0,1.5,6,0" Text="大馒头" />
<TextBlock Margin="0,1.5,6,0" Text="啊哈哈" />
<TextBlock Margin="0,1.5,6,0" Text="云与海" />
<TextBlock Margin="0,1.5,6,0" Text="V0rtex" />
<TextBlock Margin="0,1.5,6,0" Text="杨子普" />
<TextBlock Margin="0,1.5,6,0" Text="大阿科" />
<TextBlock Margin="0,1.5,6,0" Text="夷希微" />
<TextBlock Margin="0,1.5,6,0" Text="吃不饱" />
<TextBlock Margin="0,1.5,6,0" Text="爱熙熙" />
<TextBlock Margin="0,1.5,6,0" Text="丁老王" />
<TextBlock Margin="0,1.5,6,0" Text="吕雪源" />
<TextBlock Margin="0,1.5,6,0" Text="赵亦勋" />
<TextBlock Margin="0,1.5,6,0" Text="为南西" />
<TextBlock Margin="0,1.5,6,0" Text="下午觉" />
<TextBlock Margin="0,1.5,6,0" Text="李寒衣" />
<TextBlock Margin="0,1.5,6,0" Text="水不湿" />
<TextBlock Margin="0,1.5,6,0" Text="椛芷茈" />
<TextBlock Margin="0,1.5,6,0" Text="李察维" />
<TextBlock Margin="0,1.5,6,0" Text="十六夜" />
<TextBlock Margin="0,1.5,6,0" Text="皮小风" />
<TextBlock Margin="0,1.5,6,0" Text="陈知远" />
<TextBlock Margin="0,1.5,6,0" Text="TaLula" />
<TextBlock Margin="0,1.5,6,0" Text="N线🧵" />
<TextBlock Margin="0,1.5,6,0" Text="秋辞白." />
<TextBlock Margin="0,1.5,6,0" Text="Keil_v5" />
<TextBlock Margin="0,1.5,6,0" Text="laimati" />
<TextBlock Margin="0,1.5,6,0" Text="TraSok" />
<TextBlock Margin="0,1.5,6,0" Text="CH2N8" />
<TextBlock Margin="0,1.5,6,0" Text="Ls-盛望" />
<TextBlock Margin="0,1.5,6,0" Text="FiLin12" />
<TextBlock Margin="0,1.5,6,0" Text="cz_awa" />
<TextBlock Margin="0,1.5,6,0" Text="SYMYR" />
<TextBlock Margin="0,1.5,6,0" Text="524950" />
<TextBlock Margin="0,1.5,6,0" Text="Trigger" />
<TextBlock Margin="0,1.5,6,0" Text="CH南宫" />
<TextBlock Margin="0,1.5,6,0" Text="bajie79" />
<TextBlock Margin="0,1.5,6,0" Text="_xundu" />
<TextBlock Margin="0,1.5,6,0" Text="TSLOLY" />
<TextBlock Margin="0,1.5,6,0" Text="ncg303" />
<TextBlock Margin="0,1.5,6,0" Text="cqg36k" />
<TextBlock Margin="0,1.5,6,0" Text="YueYue" />
<TextBlock Margin="0,1.5,6,0" Text="火羽白S" />
<TextBlock Margin="0,1.5,6,0" Text="AMumi" />
<TextBlock Margin="0,1.5,6,0" Text="GAGUA" />
<TextBlock Margin="0,1.5,6,0" Text="shijie02" />
<TextBlock Margin="0,1.5,6,0" Text="LULULU" />
<TextBlock Margin="0,1.5,6,0" Text="iso3824" />
<TextBlock Margin="0,1.5,6,0" Text="Yununy" />
<TextBlock Margin="0,1.5,6,0" Text="pnd6ou" />
<TextBlock Margin="0,1.5,6,0" Text="Liuaovo" />
<TextBlock Margin="0,1.5,6,0" Text="Akashi7" />
<TextBlock Margin="0,1.5,6,0" Text="softtype" />
<TextBlock Margin="0,1.5,6,0" Text="dumpyy" />
<TextBlock Margin="0,1.5,6,0" Text="LO_WSY" />
<TextBlock Margin="0,1.5,6,0" Text="Mirtium" />
<TextBlock Margin="0,1.5,6,0" Text="adccadc" />
<TextBlock Margin="0,1.5,6,0" Text="Bug虫爷" />
<TextBlock Margin="0,1.5,6,0" Text="DylanPL" />
<TextBlock Margin="0,1.5,6,0" Text="moxuan" />
<TextBlock Margin="0,1.5,6,0" Text="elyruirui" />
<TextBlock Margin="0,1.5,6,0" Text="井上青叶" />
<TextBlock Margin="0,1.5,6,0" Text="懒人ROY" />
<TextBlock Margin="0,1.5,6,0" Text="殇殇月月" />
<TextBlock Margin="0,1.5,6,0" Text="日隅汐音" />
<TextBlock Margin="0,1.5,6,0" Text="小猫渔民" />
<TextBlock Margin="0,1.5,6,0" Text="凤辞时笙" />
<TextBlock Margin="0,1.5,6,0" Text="清苍星海" />
<TextBlock Margin="0,1.5,6,0" Text="芒果挺忙" />
<TextBlock Margin="0,1.5,6,0" Text="只鱼二三" />
<TextBlock Margin="0,1.5,6,0" Text="エンソウ" />
<TextBlock Margin="0,1.5,6,0" Text="高露威姿" />
<TextBlock Margin="0,1.5,6,0" Text="雪本柚子" />
<TextBlock Margin="0,1.5,6,0" Text="萌新求带" />
<TextBlock Margin="0,1.5,6,0" Text="顾炎武月" />
<TextBlock Margin="0,1.5,6,0" Text="刻记牛杂" />
<TextBlock Margin="0,1.5,6,0" Text="小飞小飞" />
<TextBlock Margin="0,1.5,6,0" Text="纯真雪豹" />
<TextBlock Margin="0,1.5,6,0" Text="罗兹瓦尔" />
<TextBlock Margin="0,1.5,6,0" Text="安静的雨" />
<TextBlock Margin="0,1.5,6,0" Text="北牧辰曦" />
<TextBlock Margin="0,1.5,6,0" Text="下次一定" />
<TextBlock Margin="0,1.5,6,0" Text="飞天土钩" />
<TextBlock Margin="0,1.5,6,0" Text="曲线生活" />
<TextBlock Margin="0,1.5,6,0" Text="管那么多" />
<TextBlock Margin="0,1.5,6,0" Text="我叫杨戬" />
<TextBlock Margin="0,1.5,6,0" Text="乐风昭苏" />
<TextBlock Margin="0,1.5,6,0" Text="丨离凡丨" />
<TextBlock Margin="0,1.5,6,0" Text="如图建系" />
<TextBlock Margin="0,1.5,6,0" Text="酒筵辞夕" />
<TextBlock Margin="0,1.5,6,0" Text="踏马的淦" />
<TextBlock Margin="0,1.5,6,0" Text="夜殇羽镰" />
<TextBlock Margin="0,1.5,6,0" Text="星尘墨韵" />
<TextBlock Margin="0,1.5,6,0" Text="聆听冰风" />
<TextBlock Margin="0,1.5,6,0" Text="鸭梨山大" />
<TextBlock Margin="0,1.5,6,0" Text="一条鱼瓯" />
<TextBlock Margin="0,1.5,6,0" Text="Kang_XL" />
<TextBlock Margin="0,1.5,6,0" Text="Crispykit" />
<TextBlock Margin="0,1.5,6,0" Text="vorinshe" />
<TextBlock Margin="0,1.5,6,0" Text="凉曦luke" />
<TextBlock Margin="0,1.5,6,0" Text="Eiluosha" />
<TextBlock Margin="0,1.5,6,0" Text="GKD小哥" />
<TextBlock Margin="0,1.5,6,0" Text="是小61呀" />
<TextBlock Margin="0,1.5,6,0" Text="Disbelief" />
<TextBlock Margin="0,1.5,6,0" Text="Manatsu" />
<TextBlock Margin="0,1.5,6,0" Text="Ace_墨尘" />
<TextBlock Margin="0,1.5,6,0" Text="Murousa" />
<TextBlock Margin="0,1.5,6,0" Text="mulberry" />
<TextBlock Margin="0,1.5,6,0" Text="lyh20218" />
<TextBlock Margin="0,1.5,6,0" Text="HIK4RI39" />
<TextBlock Margin="0,1.5,6,0" Text="Gwen_jin" />
<TextBlock Margin="0,1.5,6,0" Text="EthanCAi" />
<TextBlock Margin="0,1.5,6,0" Text="Maplefor" />
<TextBlock Margin="0,1.5,6,0" Text="Dryadove" />
<TextBlock Margin="0,1.5,6,0" Text="Xiaomo_y" />
<TextBlock Margin="0,1.5,6,0" Text="DWS3333" />
<TextBlock Margin="0,1.5,6,0" Text="EnochHcx" />
<TextBlock Margin="0,1.5,6,0" Text="hihospital" />
<WrapPanel Margin="0,0,0,10"> <TextBlock Margin="0,1.5,6,0" Text="J" />
<TextBlock Margin="0,1.5,6,0" Text="R" />
<TextBlock Margin="0,1.5,6,0" Text="D" />
<TextBlock Margin="0,1.5,6,0" Text="hj" />
<TextBlock Margin="0,1.5,6,0" Text="乖" />
<TextBlock Margin="0,1.5,6,0" Text="吕" />
<TextBlock Margin="0,1.5,6,0" Text="宇" />
<TextBlock Margin="0,1.5,6,0" Text="FF" />
<TextBlock Margin="0,1.5,6,0" Text="金" />
<TextBlock Margin="0,1.5,6,0" Text="夏" />
<TextBlock Margin="0,1.5,6,0" Text="鸿" />
<TextBlock Margin="0,1.5,6,0" Text="ツ" />
<TextBlock Margin="0,1.5,6,0" Text="枫" />
<TextBlock Margin="0,1.5,6,0" Text="SF" />
<TextBlock Margin="0,1.5,6,0" Text="kll" />
<TextBlock Margin="0,1.5,6,0" Text="02" />
<TextBlock Margin="0,1.5,6,0" Text="YY" />
<TextBlock Margin="0,1.5,6,0" Text="……" />
<TextBlock Margin="0,1.5,6,0" Text="shy" />
<TextBlock Margin="0,1.5,6,0" Text="lost" />
<TextBlock Margin="0,1.5,6,0" Text="A仔" />
<TextBlock Margin="0,1.5,6,0" Text="111" />
<TextBlock Margin="0,1.5,6,0" Text="afaf" />
<TextBlock Margin="0,1.5,6,0" Text="pen" />
<TextBlock Margin="0,1.5,6,0" Text="lulu" />
<TextBlock Margin="0,1.5,6,0" Text="xyty" />
<TextBlock Margin="0,1.5,6,0" Text="排骨" />
<TextBlock Margin="0,1.5,6,0" Text="踏雾" />
<TextBlock Margin="0,1.5,6,0" Text="Zsol" />
<TextBlock Margin="0,1.5,6,0" Text="景仪" />
<TextBlock Margin="0,1.5,6,0" Text="可可" />
<TextBlock Margin="0,1.5,6,0" Text="白夜" />
<TextBlock Margin="0,1.5,6,0" Text="汤圆" />
<TextBlock Margin="0,1.5,6,0" Text="星月" />
<TextBlock Margin="0,1.5,6,0" Text="皓月" />
<TextBlock Margin="0,1.5,6,0" Text="老建" />
<TextBlock Margin="0,1.5,6,0" Text="阿琼" />
<TextBlock Margin="0,1.5,6,0" Text="晓月" />
<TextBlock Margin="0,1.5,6,0" Text="朝雨" />
<TextBlock Margin="0,1.5,6,0" Text="迟雨" />
<TextBlock Margin="0,1.5,6,0" Text="折纸" />
<TextBlock Margin="0,1.5,6,0" Text="张某" />
<TextBlock Margin="0,1.5,6,0" Text="残月" />
<TextBlock Margin="0,1.5,6,0" Text="缘分" />
<TextBlock Margin="0,1.5,6,0" Text="静态" />
<TextBlock Margin="0,1.5,6,0" Text="铭熙" />
<TextBlock Margin="0,1.5,6,0" Text="酒狐" />
<TextBlock Margin="0,1.5,6,0" Text="誌異" />
<TextBlock Margin="0,1.5,6,0" Text="灵喵" />
<TextBlock Margin="0,1.5,6,0" Text="曦宇" />
<TextBlock Margin="0,1.5,6,0" Text="木辛" />
<TextBlock Margin="0,1.5,6,0" Text="轻梦" />
<TextBlock Margin="0,1.5,6,0" Text="熊猫" />
<TextBlock Margin="0,1.5,6,0" Text="鱼柳" />
<TextBlock Margin="0,1.5,6,0" Text="梨子" />
<TextBlock Margin="0,1.5,6,0" Text="小本" />
<TextBlock Margin="0,1.5,6,0" Text="起媛" />
<TextBlock Margin="0,1.5,6,0" Text="夏天" />
<TextBlock Margin="0,1.5,6,0" Text="泪痕" />
<TextBlock Margin="0,1.5,6,0" Text="南北" />
<TextBlock Margin="0,1.5,6,0" Text="灰鱼" />
<TextBlock Margin="0,1.5,6,0" Text="灰尘" />
<TextBlock Margin="0,1.5,6,0" Text="骗铭" />
<TextBlock Margin="0,1.5,6,0" Text="青轴" />
<TextBlock Margin="0,1.5,6,0" Text="谷雨" />
<TextBlock Margin="0,1.5,6,0" Text="呈祥" />
<TextBlock Margin="0,1.5,6,0" Text="猫玖" />
<TextBlock Margin="0,1.5,6,0" Text="枕月" />
<TextBlock Margin="0,1.5,6,0" Text="刘革" />
<TextBlock Margin="0,1.5,6,0" Text="折花" />
<TextBlock Margin="0,1.5,6,0" Text="应星" />
<TextBlock Margin="0,1.5,6,0" Text="没有" />
<TextBlock Margin="0,1.5,6,0" Text="花园" />
<TextBlock Margin="0,1.5,6,0" Text="华烬" />
<TextBlock Margin="0,1.5,6,0" Text="啥猫" />
<TextBlock Margin="0,1.5,6,0" Text="阿伯" />
<TextBlock Margin="0,1.5,6,0" Text="妄想" />
<TextBlock Margin="0,1.5,6,0" Text="树叶" />
<TextBlock Margin="0,1.5,6,0" Text="拾玖" />
<TextBlock Margin="0,1.5,6,0" Text="让人" />
<TextBlock Margin="0,1.5,6,0" Text="晓光" />
<TextBlock Margin="0,1.5,6,0" Text="懿尘" />
<TextBlock Margin="0,1.5,6,0" Text="尾巴" />
<TextBlock Margin="0,1.5,6,0" Text="枯骨" />
<TextBlock Margin="0,1.5,6,0" Text="鹤升" />
<TextBlock Margin="0,1.5,6,0" Text="遊泠" />
<TextBlock Margin="0,1.5,6,0" Text="裕茗" />
<TextBlock Margin="0,1.5,6,0" Text="沁园" />
<TextBlock Margin="0,1.5,6,0" Text="尹伊" />
<TextBlock Margin="0,1.5,6,0" Text="枫音" />
<TextBlock Margin="0,1.5,6,0" Text="古色" />
<TextBlock Margin="0,1.5,6,0" Text="傻鱼" />
<TextBlock Margin="0,1.5,6,0" Text="幻灭" />
<TextBlock Margin="0,1.5,6,0" Text="小琪" />
<TextBlock Margin="0,1.5,6,0" Text="小寒" />
<TextBlock Margin="0,1.5,6,0" Text="星狗" />
<TextBlock Margin="0,1.5,6,0" Text="玄米" />
<TextBlock Margin="0,1.5,6,0" Text="菜狗" />
<TextBlock Margin="0,1.5,6,0" Text="ZWL" />
<TextBlock Margin="0,1.5,6,0" Text="1234" />
<TextBlock Margin="0,1.5,6,0" Text=" 清空" />
<TextBlock Margin="0,1.5,6,0" Text="7104" />
<TextBlock Margin="0,1.5,6,0" Text="2333" />
<TextBlock Margin="0,1.5,6,0" Text="5-DF" />
<TextBlock Margin="0,1.5,6,0" Text="qqes" />
<TextBlock Margin="0,1.5,6,0" Text="Patxi" />
<TextBlock Margin="0,1.5,6,0" Text="Sage" />
<TextBlock Margin="0,1.5,6,0" Text="R301" />
<TextBlock Margin="0,1.5,6,0" Text="RXZY" />
<TextBlock Margin="0,1.5,6,0" Text="Siwill" />
<TextBlock Margin="0,1.5,6,0" Text="kassa" />
<TextBlock Margin="0,1.5,6,0" Text="ssdsb" />
<TextBlock Margin="0,1.5,6,0" Text="Ztdzz" />
<TextBlock Margin="0,1.5,6,0" Text="xiboa" />
<TextBlock Margin="0,1.5,6,0" Text="ZERO" />
<TextBlock Margin="0,1.5,6,0" Text="pr1nk" />
<TextBlock Margin="0,1.5,6,0" Text="BBUH" />
<TextBlock Margin="0,1.5,6,0" Text="ALISA" />
<TextBlock Margin="0,1.5,6,0" Text="89623" />
<TextBlock Margin="0,1.5,6,0" Text="IDXLZ" />
<TextBlock Margin="0,1.5,6,0" Text="翎羽絮" />
<TextBlock Margin="0,1.5,6,0" Text="硫酸梅" />
<TextBlock Margin="0,1.5,6,0" Text="死鱼梦" />
<TextBlock Margin="0,1.5,6,0" Text="秦始皇" />
<TextBlock Margin="0,1.5,6,0" Text="春忆雪" />
<TextBlock Margin="0,1.5,6,0" Text="冷空气" />
<TextBlock Margin="0,1.5,6,0" Text="天崇域" />
<TextBlock Margin="0,1.5,6,0" Text="白与沧" />
<TextBlock Margin="0,1.5,6,0" Text="柳蕴訫" />
<TextBlock Margin="0,1.5,6,0" Text="苟子丶" />
<TextBlock Margin="0,1.5,6,0" Text="午時夢" />
<TextBlock Margin="0,1.5,6,0" Text="清水石" />
<TextBlock Margin="0,1.5,6,0" Text="鹿鹤云" />
<TextBlock Margin="0,1.5,6,0" Text="洛沫兀" />
<TextBlock Margin="0,1.5,6,0" Text="。。。" />
<TextBlock Margin="0,1.5,6,0" Text="樱咲夜" />
<TextBlock Margin="0,1.5,6,0" Text="星期天" />
<TextBlock Margin="0,1.5,6,0" Text="制花人" />
<TextBlock Margin="0,1.5,6,0" Text="aleMk" />
<TextBlock Margin="0,1.5,6,0" Text="张炜国" />
<TextBlock Margin="0,1.5,6,0" Text="咦嘻嘻" />
<TextBlock Margin="0,1.5,6,0" Text="拟态虫" />
<TextBlock Margin="0,1.5,6,0" Text="蜥蜴拔" />
<TextBlock Margin="0,1.5,6,0" Text="鬼画符" />
<TextBlock Margin="0,1.5,6,0" Text="孙无为" />
<TextBlock Margin="0,1.5,6,0" Text="浅陌菌" />
<TextBlock Margin="0,1.5,6,0" Text="黯域星" />
<TextBlock Margin="0,1.5,6,0" Text="愿不惑" />
<TextBlock Margin="0,1.5,6,0" Text="三色堇" />
<TextBlock Margin="0,1.5,6,0" Text="訫卜變" />
<TextBlock Margin="0,1.5,6,0" Text="周家逸" />
<TextBlock Margin="0,1.5,6,0" Text="水芃叁" />
<TextBlock Margin="0,1.5,6,0" Text="无限旡" />
<TextBlock Margin="0,1.5,6,0" Text="Enrico" />
<TextBlock Margin="0,1.5,6,0" Text="???" />
<TextBlock Margin="0,1.5,6,0" Text="小南新" />
<TextBlock Margin="0,1.5,6,0" Text="星千织" />
<TextBlock Margin="0,1.5,6,0" Text="CX330" />
<TextBlock Margin="0,1.5,6,0" Text="林空月" />
<TextBlock Margin="0,1.5,6,0" Text="花开丶" />
<TextBlock Margin="0,1.5,6,0" Text="琉璃子" />
<TextBlock Margin="0,1.5,6,0" Text="冰冰冰" />
<TextBlock Margin="0,1.5,6,0" Text="老花眼" />
<TextBlock Margin="0,1.5,6,0" Text="哈贝吧" />
<TextBlock Margin="0,1.5,6,0" Text="蘑菇头" />
<TextBlock Margin="0,1.5,6,0" Text="小黄刀" />
<TextBlock Margin="0,1.5,6,0" Text="大破林" />
<TextBlock Margin="0,1.5,6,0" Text="倾伏离" />
<TextBlock Margin="0,1.5,6,0" Text="豆角君" />
<TextBlock Margin="0,1.5,6,0" Text="灵玄机" />
<TextBlock Margin="0,1.5,6,0" Text="Sherry" />
<TextBlock Margin="0,1.5,6,0" Text="lizi756" />
<TextBlock Margin="0,1.5,6,0" Text="letitbe" />
<TextBlock Margin="0,1.5,6,0" Text="Ikorito" />
<TextBlock Margin="0,1.5,6,0" Text="MCDD" />
<TextBlock Margin="0,1.5,6,0" Text="nihility" />
<TextBlock Margin="0,1.5,6,0" Text="小宇Bo" />
<TextBlock Margin="0,1.5,6,0" Text="2789th" />
<TextBlock Margin="0,1.5,6,0" Text="S1NKO" />
<TextBlock Margin="0,1.5,6,0" Text="Rimlue" />
<TextBlock Margin="0,1.5,6,0" Text="FMDQI" />
<TextBlock Margin="0,1.5,6,0" Text="Aevrud" />
<TextBlock Margin="0,1.5,6,0" Text="Larry76" />
<TextBlock Margin="0,1.5,6,0" Text="Miantu" />
<TextBlock Margin="0,1.5,6,0" Text="Nature." />
<TextBlock Margin="0,1.5,6,0" Text="Autism." />
<TextBlock Margin="0,1.5,6,0" Text="MXUER" />
<TextBlock Margin="0,1.5,6,0" Text="suifeng" />
<TextBlock Margin="0,1.5,6,0" Text="Yangeh" />
<TextBlock Margin="0,1.5,6,0" Text="Anmisu" />
<TextBlock Margin="0,1.5,6,0" Text="TinkerZ" />
<TextBlock Margin="0,1.5,6,0" Text="LaoGou" />
<TextBlock Margin="0,1.5,6,0" Text="lkhkykk" />
<TextBlock Margin="0,1.5,6,0" Text="N/Azais" />
<TextBlock Margin="0,1.5,6,0" Text="KISSHIP" />
<TextBlock Margin="0,1.5,6,0" Text="Vanessa" />
<TextBlock Margin="0,1.5,6,0" Text="lanyuan" />
<TextBlock Margin="0,1.5,6,0" Text="BBBKKK" />
<TextBlock Margin="0,1.5,6,0" Text="mchim1" />
<TextBlock Margin="0,1.5,6,0" Text="MANBA" />
<TextBlock Margin="0,1.5,6,0" Text="Tairitsu_" />
<TextBlock Margin="0,1.5,6,0" Text="宇陌୧⍤⃝🇨🇳" />
<TextBlock Margin="0,1.5,6,0" Text="我不在线" />
<TextBlock Margin="0,1.5,6,0" Text="南吕十三" />
<TextBlock Margin="0,1.5,6,0" Text="Parapha" />
<TextBlock Margin="0,1.5,6,0" Text="开始学习" />
<TextBlock Margin="0,1.5,6,0" Text="对方和你" />
<TextBlock Margin="0,1.5,6,0" Text="雪辰千叶" />
<TextBlock Margin="0,1.5,6,0" Text="残风尘缘" />
<TextBlock Margin="0,1.5,6,0" Text="黄粱一梦" />
<TextBlock Margin="0,1.5,6,0" Text="腊肉清蒸" />
<TextBlock Margin="0,1.5,6,0" Text="画下花夏" />
<TextBlock Margin="0,1.5,6,0" Text="蛮列后备" />
<TextBlock Margin="0,1.5,6,0" Text="魔法少年" />
<TextBlock Margin="0,1.5,6,0" Text="戒者有八" />
<TextBlock Margin="0,1.5,6,0" Text="躺平如尸" />
<TextBlock Margin="0,1.5,6,0" Text="萝卜奶酪" />
<TextBlock Margin="0,1.5,6,0" Text="Erinneys" />
<TextBlock Margin="0,1.5,6,0" Text="病名為愛" />
<TextBlock Margin="0,1.5,6,0" Text="伊卡洛斯" />
<TextBlock Margin="0,1.5,6,0" Text="云鹰肥将" />
<TextBlock Margin="0,1.5,6,0" Text="爱莉信徒" />
<TextBlock Margin="0,1.5,6,0" Text="user晚曦" />
<TextBlock Margin="0,1.5,6,0" Text="欣某在此" />
<TextBlock Margin="0,1.5,6,0" Text="繁星末路" />
<TextBlock Margin="0,1.5,6,0" Text="鲸鱼橘子" />
<TextBlock Margin="0,1.5,6,0" Text="坠入幻想" />
<TextBlock Margin="0,1.5,6,0" Text="卑微老万" />
<TextBlock Margin="0,1.5,6,0" Text="乌云踏雪" />
<TextBlock Margin="0,1.5,6,0" Text="公积金卡" />
<TextBlock Margin="0,1.5,6,0" Text="各务原念" />
<TextBlock Margin="0,1.5,6,0" Text="风间琉璃" />
<TextBlock Margin="0,1.5,6,0" Text="杨枝甘露" />
<TextBlock Margin="0,1.5,6,0" Text="晚安晚安" />
<TextBlock Margin="0,1.5,6,0" Text="牛奶曲奇" />
<TextBlock Margin="0,1.5,6,0" Text="魔法空间" />
<TextBlock Margin="0,1.5,6,0" Text="一路向北" />
<TextBlock Margin="0,1.5,6,0" Text="百里无焉" />
<TextBlock Margin="0,1.5,6,0" Text="皮蛋肥猫" />
<TextBlock Margin="0,1.5,6,0" Text="身披坚甲" />
<TextBlock Margin="0,1.5,6,0" Text="近则不逊" />
<TextBlock Margin="0,1.5,6,0" Text="卡白小洛" />
<TextBlock Margin="0,1.5,6,0" Text="即墨非墨" />
<TextBlock Margin="0,1.5,6,0" Text="准备冒险" />
<TextBlock Margin="0,1.5,6,0" Text="喰梦优音" />
<TextBlock Margin="0,1.5,6,0" Text="雨夜色中" />
<TextBlock Margin="0,1.5,6,0" Text="沃特拉奇" />
<TextBlock Margin="0,1.5,6,0" Text="闲鱼教授" />
<TextBlock Margin="0,1.5,6,0" Text="北川暮雨" />
<TextBlock Margin="0,1.5,6,0" Text="生根发芽" />
<TextBlock Margin="0,1.5,6,0" Text="暮霭凝缘" />
<TextBlock Margin="0,1.5,6,0" Text="孤灯裹尘" />
<TextBlock Margin="0,1.5,6,0" Text="日薪月亿" />
<TextBlock Margin="0,1.5,6,0" Text="北默千鹤" />
<TextBlock Margin="0,1.5,6,0" Text="咸鱼一条" />
<TextBlock Margin="0,1.5,6,0" Text="禾辞共振" />
<TextBlock Margin="0,1.5,6,0" Text="雨宫冰莲" />
<TextBlock Margin="0,1.5,6,0" Text="BG4LHO" />
<TextBlock Margin="0,1.5,6,0" Text="没啥想法" />
<TextBlock Margin="0,1.5,6,0" Text="古色沉竹" />
<TextBlock Margin="0,1.5,6,0" Text="W污狮W" />
<TextBlock Margin="0,1.5,6,0" Text="jinyucao" />
<TextBlock Margin="0,1.5,6,0" Text="MESGRZ" />
<TextBlock Margin="0,1.5,6,0" Text="qiewang" />
<TextBlock Margin="0,1.5,6,0" Text="bao9898" />
<TextBlock Margin="0,1.5,6,0" Text="幻沫_123" />
<TextBlock Margin="0,1.5,6,0" Text="Yuting.Li" />
<TextBlock Margin="0,1.5,6,0" Text="Zaaaako" />
<TextBlock Margin="0,1.5,6,0" Text="Tianqiao" />
<TextBlock Margin="0,1.5,6,0" Text="EMILIIIIA" />
<TextBlock Margin="0,1.5,6,0" Text="阿尔法.@" />
<TextBlock Margin="0,1.5,6,0" Text="neymarjr" />
<TextBlock Margin="0,1.5,6,0" Text="AsUWish" />
<TextBlock Margin="0,1.5,6,0" Text="waxberry" />
<TextBlock Margin="0,1.5,6,0" Text="彼岸的***" />
<TextBlock Margin="0,1.5,6,0" Text="liminalily" />
<TextBlock Margin="0,1.5,6,0" Text="sansisthe" />
<TextBlock Margin="0,1.5,6,0" Text="RickyZhu" />
<TextBlock Margin="0,1.5,6,0" Text="TenKings" />
<TextBlock Margin="0,1.5,6,0" Text="yvnchvan" />
<TextBlock Margin="0,1.5,6,0" Text="喵呜派zzz" />
<TextBlock Margin="0,1.5,6,0" Text="Mz小狐仙" />
<TextBlock Margin="0,1.5,6,0" Text="huangwu" />
<TextBlock Margin="0,1.5,6,0" Text="玖月Yiyue" />
<TextBlock Margin="0,1.5,6,0" Text="石头WAW" />
<TextBlock Margin="0,1.5,6,0" Text="YiYi_Sama" />
<TextBlock Margin="0,1.5,6,0" Text="theancato" />
<TextBlock Margin="0,1.5,6,0" Text="Feng_mee" />
<TextBlock Margin="0,1.5,6,0" Text="_東森浮星_" />
<TextBlock Margin="0,1.5,6,0" Text="Com_XDN" />
<TextBlock Margin="0,1.5,6,0" Text="SEIGLE404" />
<TextBlock Margin="0,1.5,6,0" Text="Kevin2022" />
<TextBlock Margin="0,1.5,6,0" Text="梓晨Otaku" />
<TextBlock Margin="0,1.5,6,0" Text="凌点工作室" />
<TextBlock Margin="0,1.5,6,0" Text="千秋万载猹" />
<TextBlock Margin="0,1.5,6,0" Text="超级大月光" />
<TextBlock Margin="0,1.5,6,0" Text="精酿糯米酒" />
<TextBlock Margin="0,1.5,6,0" Text="不「成」器" />
<TextBlock Margin="0,1.5,6,0" Text="无常之客星" />
<TextBlock Margin="0,1.5,6,0" Text="第一年冬天" />
<TextBlock Margin="0,1.5,6,0" Text="大王上早八" />
<TextBlock Margin="0,1.5,6,0" Text="爱吃小笼包" />
<TextBlock Margin="0,1.5,6,0" Text="红楼彡揽月" />
<TextBlock Margin="0,1.5,6,0" Text="画海听竹者" />
<TextBlock Margin="0,1.5,6,0" Text="白哥咯咯咯" />
<TextBlock Margin="0,1.5,6,0" Text="懒得喷喷喷" />
<TextBlock Margin="0,1.5,6,0" Text="估计不会玩" />
<TextBlock Margin="0,1.5,6,0" Text="伊南宁门特" />
<TextBlock Margin="0,1.5,6,0" Text="秋山有希子" />
<TextBlock Margin="0,1.5,6,0" Text="氧化氧化氧" />
<TextBlock Margin="0,1.5,6,0" Text="傻悠不知道" />
<TextBlock Margin="0,1.5,6,0" Text="不爱吃灼芋" />
<TextBlock Margin="0,1.5,6,0" Text="山枕檀痕涴" />
<TextBlock Margin="0,1.5,6,0" Text="你是妖怪叭" />
<TextBlock Margin="0,1.5,6,0" Text="不知名玩家" />
<TextBlock Margin="0,1.5,6,0" Text="湛蓝一四七" />
<TextBlock Margin="0,1.5,6,0" Text="翰墨续丹青" />
<TextBlock Margin="0,1.5,6,0" Text="凛果与百橙" />
<TextBlock Margin="0,1.5,6,0" Text="Touma000" />
<TextBlock Margin="0,1.5,6,0" Text="回忆de天空" />
<TextBlock Margin="0,1.5,6,0" Text="Yunlimeng" />
<TextBlock Margin="0,1.5,6,0" Text="GODxianyu" />
<TextBlock Margin="0,1.5,6,0" Text="Yhcmdbfwz" />
<TextBlock Margin="0,1.5,6,0" Text="Equilibrium" />
<TextBlock Margin="0,1.5,6,0" Text="陌泽陌泽MZ" />
<TextBlock Margin="0,1.5,6,0" Text="咖啡R3borN" />
<TextBlock Margin="0,1.5,6,0" Text="1234567890" />
<TextBlock Margin="0,1.5,6,0" Text="GUGUGAGA" />
<TextBlock Margin="0,1.5,6,0" Text="2366445223" />
<TextBlock Margin="0,1.5,6,0" Text="arrovartsk77" />
<TextBlock Margin="0,1.5,6,0" Text="Elegytairitsu" />
<TextBlock Margin="0,1.5,6,0" Text="南小彩的梦想" />
<TextBlock Margin="0,1.5,6,0" Text="最后一只斑鸠" />
<TextBlock Margin="0,1.5,6,0" Text="刻晴天下第一" />
<TextBlock Margin="0,1.5,6,0" Text="幸运的蓝耀星" />
<TextBlock Margin="0,1.5,6,0" Text="整没用的红球" />
<TextBlock Margin="0,1.5,6,0" Text="端午节吃饺子" />
<TextBlock Margin="0,1.5,6,0" Text="DLurker7840" />
<TextBlock Margin="0,1.5,6,0" Text="Don_Quixote" />
<TextBlock Margin="0,1.5,6,0" Text="funnyzhuling" />
<TextBlock Margin="0,1.5,6,0" Text="AYGY暗夜古韵" />
<TextBlock Margin="0,1.5,6,0" Text="Kecdl_Chacha" />
<TextBlock Margin="0,1.5,6,0" Text="对方正在输入 ..." />
<TextBlock Margin="0,1.5,6,0" Text="Chihaya-Anon" />
<TextBlock Margin="0,1.5,6,0" Text="虚域染指亦起舞" />
<TextBlock Margin="0,1.5,6,0" Text="半人半灵的麻薯" />
<TextBlock Margin="0,1.5,6,0" Text="霜雪焉能见太阳" />
<TextBlock Margin="0,1.5,6,0" Text="章鱼哥的琪露诺" />
<TextBlock Margin="0,1.5,6,0" Text="重生之发财传奇" />
<TextBlock Margin="0,1.5,6,0" Text="某科学的小狐狸" />
<TextBlock Margin="0,1.5,6,0" Text="AAA神秘人批发" />
<TextBlock Margin="0,1.5,6,0" Text="贝吉蒙Begimon" />
<TextBlock Margin="0,1.5,6,0" Text="Kozume_Kenma" />
<TextBlock Margin="0,1.5,6,0" Text="Taskmgr-MEMZ" />
<TextBlock Margin="0,1.5,6,0" Text="漫天星火为谁舞?" />
<TextBlock Margin="0,1.5,6,0" Text="常凱申(刘小胖)" />
<TextBlock Margin="0,1.5,6,0" Text="赚够三千万就瘦瘦" />
<TextBlock Margin="0,1.5,6,0" Text="大道五十天衍四九" />
<TextBlock Margin="0,1.5,6,0" Text="爱停电用户_查尔德" />
<TextBlock Margin="0,1.5,6,0" Text="狸猫曾在学校烤面包" />
<TextBlock Margin="0,1.5,6,0" Text="(´o̴̶̷̤ㅂo̴̶̷̤`)" />
<TextBlock Margin="0,1.5,6,0" Text="克洛伊.冯.爱因兹贝伦" />
<TextBlock Margin="0,1.5,6,0" Text="(*^ワ^*)^_^$$" />
<TextBlock Margin="0,1.5,6,0" Text="GongzhuHuanerAwA" />
<TextBlock Margin="0,1.5,6,0" Text="幽兰黛尔【Druandal】" />
<TextBlock Margin="0,1.5,6,0" Text="codm我要玩一辈子cx-9" />
<TextBlock Margin="0,1.5,6,0" Text="dark 寿司s" />
<TextBlock Margin="0,1.5,6,0" Text="zhouuohz" />
<TextBlock Margin="0,1.5,6,0" Text="躺床上发霉" />
<TextBlock Margin="0,1.5,6,0" Text="装睡的少爷" />
<TextBlock Margin="0,1.5,6,0" Text="无想的贤者" />
<TextBlock Margin="0,1.5,6,0" Text="我不是陌行" />
<TextBlock Margin="0,1.5,6,0" Text="海绵体宝宝" />
<TextBlock Margin="0,1.5,6,0" Text="猫不吃咸鱼" />
<TextBlock Margin="0,1.5,6,0" Text="别太失望啦" />
<TextBlock Margin="0,1.5,6,0" Text="土豆粉底液" />
<TextBlock Margin="0,1.5,6,0" Text="沈默已陌鑫" />
<TextBlock Margin="0,1.5,6,0" Text="向神明祈祷" />
<TextBlock Margin="0,1.5,6,0" Text="Zurkey106" />
<TextBlock Margin="0,1.5,6,0" Text="stargolven" />
<TextBlock Margin="0,1.5,6,0" Text="拒绝过阿姨" />
<TextBlock Margin="0,1.5,6,0" Text="拟态深埋箱" />
<TextBlock Margin="0,1.5,6,0" Text="凤凰院凶真" />
<TextBlock Margin="0,1.5,6,0" Text="时间&轮盘" />
<TextBlock Margin="0,1.5,6,0" Text="无心于刹那" />
<TextBlock Margin="0,1.5,6,0" Text="粗心忘带头" />
<TextBlock Margin="0,1.5,6,0" Text="提线的人偶" />
<TextBlock Margin="0,1.5,6,0" Text="非慕所不敬" />
<TextBlock Margin="0,1.5,6,0" Text="天生会炸鱼" />
<TextBlock Margin="0,1.5,6,0" Text="世界第一矮" />
<TextBlock Margin="0,1.5,6,0" Text="雷焰不虚误" />
<TextBlock Margin="0,1.5,6,0" Text="一株腐竹吖" />
<TextBlock Margin="0,1.5,6,0" Text="七曜不打针" />
<TextBlock Margin="0,1.5,6,0" Text="Ferrum000" />
<TextBlock Margin="0,1.5,6,0" Text="小白pandk" />
<TextBlock Margin="0,1.5,6,0" Text="LapalceXIII" />
<TextBlock Margin="0,1.5,6,0" Text="charlesmin" />
<TextBlock Margin="0,1.5,6,0" Text="DeepSleep" />
<TextBlock Margin="0,1.5,6,0" Text="rishaoshan" />
<TextBlock Margin="0,1.5,6,0" Text="LJJ有擎天柱" />
<TextBlock Margin="0,1.5,6,0" Text="builder天识" />
<TextBlock Margin="0,1.5,6,0" Text="Cathan猫皁" />
<TextBlock Margin="0,1.5,6,0" Text="猫熊NICEST" />
<TextBlock Margin="0,1.5,6,0" Text="heaven伯特" />
<TextBlock Margin="0,1.5,6,0" Text="礼央子sama" />
<TextBlock Margin="0,1.5,6,0" Text="蠢萌der小白" />
<TextBlock Margin="0,1.5,6,0" Text="Dying Bone" />
<TextBlock Margin="0,1.5,6,0" Text="DaisyCrown" />
<TextBlock Margin="0,1.5,6,0" Text="2127864647" />
<TextBlock Margin="0,1.5,6,0" Text="Z123456789" />
<TextBlock Margin="0,1.5,6,0" Text="MOMOHAN" />
<TextBlock Margin="0,1.5,6,0" Text="舸沫_Official" />
<TextBlock Margin="0,1.5,6,0" Text="ξKō Hei-seiξ" />
<TextBlock Margin="0,1.5,6,0" Text="CookieNeko" />
<TextBlock Margin="0,1.5,6,0" Text="伊吹风子大人" />
<TextBlock Margin="0,1.5,6,0" Text="バベルの悪霊" />
<TextBlock Margin="0,1.5,6,0" Text="沉默的玄晓羲" />
<TextBlock Margin="0,1.5,6,0" Text="为什么水无鱼" />
<TextBlock Margin="0,1.5,6,0" Text="无敌猫猫刺客" />
<TextBlock Margin="0,1.5,6,0" Text="久恋丿如初漾" />
<TextBlock Margin="0,1.5,6,0" Text="肥宅不是胖子" />
<TextBlock Margin="0,1.5,6,0" Text="路边的小野寺" />
<TextBlock Margin="0,1.5,6,0" Text="要一个手抓饼" />
<TextBlock Margin="0,1.5,6,0" Text="从未擦肩而过" />
<TextBlock Margin="0,1.5,6,0" Text="瓜皮的大甘蔗" />
<TextBlock Margin="0,1.5,6,0" Text="LI_JIACHENG" />
<TextBlock Margin="0,1.5,6,0" Text="mouyangjun" />
<TextBlock Margin="0,1.5,6,0" Text="我no想取名字" />
<TextBlock Margin="0,1.5,6,0" Text="机电2516汪帅" />
<TextBlock Margin="0,1.5,6,0" Text="QueNanNNN" />
<TextBlock Margin="0,1.5,6,0" Text="huamozhuke" />
<TextBlock Margin="0,1.5,6,0" Text="Titanic Rising" />
<TextBlock Margin="0,1.5,6,0" Text="Skymunchrex" />
<TextBlock Margin="0,1.5,6,0" Text="☆百☆岁☆枭☆" />
<TextBlock Margin="0,1.5,6,0" Text="CarbonDHeat" />
<TextBlock Margin="0,1.5,6,0" Text="不爱fa电的小安" />
<TextBlock Margin="0,1.5,6,0" Text="是enderman噢" />
<TextBlock Margin="0,1.5,6,0" Text="无敌兵王陈亚蒙" />
<TextBlock Margin="0,1.5,6,0" Text="申鹤红绳牵我魂" />
<TextBlock Margin="0,1.5,6,0" Text="背不完的郭同学" />
<TextBlock Margin="0,1.5,6,0" Text="追随自己的星星" />
<TextBlock Margin="0,1.5,6,0" Text="帅道无上大宗师" />
<TextBlock Margin="0,1.5,6,0" Text="LucidZuowang" />
<TextBlock Margin="0,1.5,6,0" Text="AAA.果粒橙批发" />
<TextBlock Margin="0,1.5,6,0" Text="Omega•Epsilon" />
<TextBlock Margin="0,1.5,6,0" Text="Ries1ing_嗷菲兽" />
<TextBlock Margin="0,1.5,6,0" Text="Understand You" />
<TextBlock Margin="0,1.5,6,0" Text="AAADasAbi9527" />
<TextBlock Margin="0,1.5,6,0" Text="筱筱雨吖_Official" />
<TextBlock Margin="0,1.5,6,0" Text="DecadeMagenta" />
<TextBlock Margin="0,1.5,6,0" Text="0223YUKIUKOORI" />
<TextBlock Margin="0,1.5,6,0" Text="☆Etritz__㯊龝.ET☆" />
<TextBlock Margin="0,1.5,6,0" Text="NineTailed123213" />
<TextBlock Margin="0,1.5,6,0" Text="于电子星海寻极星间" />
<TextBlock Margin="0,1.5,6,0" Text="``Aurora. (极光)" />
<TextBlock Margin="0,1.5,6,0" Text="来自少女前线的刀客塔" />
<TextBlock Margin="0,1.5,6,0" Text="￴￴ ￴￴此恨绵绵无绝期" />
<TextBlock Margin="0,1.5,6,0" Text="[email protected]" />
<TextBlock Margin="0,1.5,6,0" Text="AdeleineSummy的粉丝" />
<TextBlock Margin="0,1.5,6,0" Text="有趣的灵魂肘击无趣的肉体" />
<TextBlock Margin="0,1.5,6,0" Text="我是pipikeng我喂自己袋盐" />
<TextBlock Margin="0,1.5,6,0" Text="人族人亚族人属智人种晚期智人亚种" />
</WrapPanel>
<Grid Height="35">
<Grid.ColumnDefinitions>
@@ -539,14 +602,14 @@
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://opensource.org/licenses/MIT" />
<TextBlock Grid.Row="6" Grid.Column="0" Text="NAudio" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2015 Mark Heath ([email protected])&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
<TextBlock Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2015 Mark Heath ([email protected])&#xa;Licensed under the Microsoft Public License (Ms-PL)." TextWrapping="Wrap" />
<local:MyButton Grid.Row="7" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/naudio/NAudio" />
<local:MyButton Grid.Row="7" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/naudio/NAudio/blob/master/license.txt" />
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/naudio/NAudio/blob/f98664ba3d5966a736dd5cf45329e542be5fece8/license.txt" />
<TextBlock Grid.Row="8" Grid.Column="0" Text="Ookii.Dialogs" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2009 Sven Groot (Ookii.org)" TextWrapping="Wrap" />
<TextBlock Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2009 Sven Groot (Ookii.org)&#xa;Licensed under the BSD 3-Clause License." TextWrapping="Wrap" />
<local:MyButton Grid.Row="9" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.ookii.org/Software/Dialogs/" />
<local:MyButton Grid.Row="9" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
@@ -568,29 +631,36 @@
<TextBlock Grid.Row="14" Grid.Column="0" Text="Iconfont" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © ALIMAMA MUX, powered by alimama THX." TextWrapping="Wrap" />
<local:MyButton Grid.Row="15" Grid.Column="2" MinWidth="140" Text="查看原网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="15" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.iconfont.cn/" />
<TextBlock Grid.Row="16" Grid.Column="0" Text="EasyTier" TextWrapping="Wrap" FontWeight="Bold" />
<!--<TextBlock Grid.Row="16" Grid.Column="0" Text="EasyTier" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="16" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2024-present Easytier Programme within The Commons Conservancy&#xa;Licensed under the GNU Lesser General Public License." TextWrapping="Wrap" />
<local:MyButton Grid.Row="17" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://easytier.cn/" />
<local:MyButton Grid.Row="17" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/EasyTier/EasyTier/blob/main/LICENSE" />
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/EasyTier/EasyTier/blob/main/LICENSE" />-->
<TextBlock Grid.Row="18" Grid.Column="0" Text="ThrottleDebounce" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="18" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2026 Ben Hutchison&#xa;Licensed under the Apache License, Version 2.0." TextWrapping="Wrap" />
<local:MyButton Grid.Row="19" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="16" Grid.Column="0" Text="ThrottleDebounce" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="16" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2026 Ben Hutchison&#xa;Licensed under the Apache License, Version 2.0." TextWrapping="Wrap" />
<local:MyButton Grid.Row="17" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/Aldaviva/ThrottleDebounce" />
<local:MyButton Grid.Row="19" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="17" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://licenses.nuget.org/Apache-2.0" />
<TextBlock Grid.Row="20" Grid.Column="0" Text="CacheCow" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="20" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © Ali Kheyrollahi&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
<local:MyButton Grid.Row="21" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="18" Grid.Column="0" Text="CacheCow" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="18" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © Ali Kheyrollahi&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
<local:MyButton Grid.Row="19" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/aliostad/CacheCow" />
<local:MyButton Grid.Row="21" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="19" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://licenses.nuget.org/MIT" />
<TextBlock Grid.Row="20" Grid.Column="0" Text="Transformer Discovery Service" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="20" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2020 huangyuhui &lt;[email protected]&gt; and contributors&#xa;Licensed under the GNU General Public License, Version 3 or later, with additional terms.&#xa;本组件作为独立组件分发,可依 GPL 复制、修改及再分发。" TextWrapping="Wrap" />
<local:MyButton Grid.Row="21" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/HMCL-dev/HMCL/tree/main/minecraft/libraries/HMCLTransformerDiscoveryService" />
<local:MyButton Grid.Row="21" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/HMCL-dev/HMCL/blob/main/LICENSE" />
</Grid>
</local:MyCard>
</StackPanel>
@@ -27,5 +27,8 @@ Public Class PageOtherAbout
Private Sub BtnDonateOutput_Click(sender As Object, e As EventArgs) Handles BtnDonateOutput.Click
GeneratePotatoCode()
End Sub
Private Sub BtnAboutUpdate_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnAboutUpdate.Click
UpdateCheckByButton()
End Sub
End Class
@@ -48,12 +48,13 @@ Public Class PageOtherHelp
If Item.Types.Contains(Type) Then TypeItems.Add(Item)
Next
'增加卡片
Dim NewCard As New MyCard With {.Title = Type, .Margin = New Thickness(0, 0, 0, 15), .SwapType = 11}
Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = TypeItems}
Dim NewCard As New MyCard With {.Title = Type, .Margin = New Thickness(0, 0, 0, 15)}
Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0)}
NewCard.Children.Add(NewStack)
NewCard.SwapControl = NewStack
NewCard.SetContentBuilder(TypeItems, Function(Entry) Entry.ToListItem)
If Type = "指南" Then
MyCard.StackInstall(NewStack, 11, "指南")
NewCard.BuildContent()
Else
NewCard.IsSwapped = True
End If
@@ -64,7 +64,7 @@ Public Class PageSelectLeft
</ContextMenu>
)
End Select
If (Folder.Type = McFolder.Types.Vanilla OrElse Folder.Type = McFolder.Types.RenamedVanilla) AndAlso Folder.Location = Paths.Base & ".minecraft\" AndAlso McFolderList.IsSingle Then CType(ContMenu.FindName("Delete"), MyMenuItem).Header = "清空"
If (Folder.Type = McFolder.Types.Vanilla OrElse Folder.Type = McFolder.Types.RenamedVanilla) AndAlso PathUtils.IsSame(Folder.Location, Paths.Base & ".minecraft\") AndAlso McFolderList.IsSingle Then CType(ContMenu.FindName("Delete"), MyMenuItem).Header = "清空"
'注册事件
If Not Folder.Type = McFolder.Types.Vanilla Then CType(ContMenu.FindName("Remove"), MyMenuItem).AddHandler(MyMenuItem.ClickEvent, New RoutedEventHandler(AddressOf FrmSelectLeft.Remove_Click))
CType(ContMenu.FindName("Open"), MyMenuItem).AddHandler(MyMenuItem.ClickEvent, New RoutedEventHandler(AddressOf FrmSelectLeft.Open_Click))
@@ -116,7 +116,7 @@ Public Class PageSelectLeft
'确认勾选状态
For i = 0 To McFolderList.Count - 1
If McFolderList(i).Location = McFolderSelected Then
If PathUtils.IsSame(McFolderList(i).Location, McFolderSelected) Then
CType(FrmSelectLeft.PanList.Children(i + 1), MyListItem).Checked = True '去掉第一个标题
Return
End If
@@ -139,9 +139,9 @@ Public Class PageSelectLeft
'添加文件夹
Public Sub Add_Click()
Dim NewFolder As String = ""
'检查是否有下载任务
If HasDownloadingTask() Then
Hint("在下载任务进行时,无法添加游戏文件夹!", HintType.Red)
'下载或版本修改期间禁止更改游戏文件夹
If McInstanceLock.IsAnyLocked OrElse HasDownloadingTask() Then
Hint("请在其他任务完成后,再添加游戏文件夹!", HintType.Red)
Return
End If
Try
@@ -150,8 +150,11 @@ Public Class PageSelectLeft
If NewFolder Is Nothing Then Return
If NewFolder.Contains("!") OrElse NewFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Red) : Return
'要求输入显示名称
Dim SplitedNames As String() = NewFolder.TrimEnd("\").Split("\")
Dim DefaultName As String = If(SplitedNames.Last = ".minecraft", If(SplitedNames.Count >= 3, SplitedNames(SplitedNames.Count - 2), ""), SplitedNames.Last)
Dim DefaultName = PathUtils.GetLastPart(NewFolder)
If DefaultName = ".minecraft" Then
Dim ParentFolder = PathUtils.RemoveLastPart(NewFolder)
DefaultName = If(ParentFolder.EndsWithF(":"), "", PathUtils.GetLastPart(ParentFolder))
End If
If DefaultName.Length > 40 Then DefaultName = DefaultName.Substring(0, 39)
Dim NewName As String = MyMsgBoxInput("输入显示名称", "输入该文件夹在左边栏列表中显示的名称。", DefaultName,
New ObjectModel.Collection(Of Validate) From {New ValidateNullOrWhiteSpace, New ValidateLength(1, 30), New ValidateExcept({">", "|"})})
@@ -169,7 +172,7 @@ Public Class PageSelectLeft
RunInThread(
Sub()
Try
If Not FolderPath.EndsWithF("\") Then FolderPath &= "\"
FolderPath = PathUtils.AddSlashSuffix(FolderPath)
'检查文件夹权限
If Not CheckPermission(FolderPath) Then
If ShowHint Then
@@ -195,7 +198,7 @@ Public Class PageSelectLeft
For i = 0 To Folders.Count - 1
Dim Folder As String = Folders(i)
If Folder = "" Then Continue For
If Folder.Split(">")(1) = FolderPath Then
If PathUtils.IsSame(Folder.Split(">")(1), FolderPath) Then
IsAdded = True
If Folder.Split(">")(0) = DisplayName Then
If ShowHint Then Hint("此文件夹已在列表中!", HintType.Blue)
@@ -246,9 +249,9 @@ Public Class PageSelectLeft
'创建文件夹
Public Sub Create_Click()
'检查是否有下载任务
If HasDownloadingTask() Then
Hint("在下载任务进行时,无法创建游戏文件夹!", HintType.Red)
'下载或版本修改期间禁止更改游戏文件夹
If McInstanceLock.IsAnyLocked OrElse HasDownloadingTask() Then
Hint("请在其他任务完成后,再创建游戏文件夹!", HintType.Red)
Return
End If
CreateMcFolderInCurrentPath()
@@ -270,6 +273,10 @@ Public Class PageSelectLeft
Dim Target As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag
'若为 “移除”,则提醒是否删除 PCL 的配置文件
If Target.Type = McFolder.Types.Custom Then
If McInstanceLock.IsAnyLocked OrElse HasDownloadingTask() Then
Hint("请在其他任务完成后,再移除游戏文件夹!", HintType.Red)
Return
End If
Select Case MyMsgBox("是否需要清理 PCL 在该文件夹中的配置文件?" & vbCrLf & "这包括各个版本的独立设置(如自定义图标、第三方登录配置)等,对游戏本身没有影响。", "配置文件清理", "删除", "保留", "取消")
Case 1
'删除配置文件
@@ -292,6 +299,10 @@ Public Class PageSelectLeft
End Try
End Sub
Public Sub Delete_Click(sender As Object, e As RoutedEventArgs)
If McInstanceLock.IsAnyLocked OrElse HasDownloadingTask() Then
Hint("请在其他任务完成后,再删除游戏文件夹!", HintType.Red)
Return
End If
Dim Target As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag
Dim DeleteText As String = If((Target.Type = McFolder.Types.Vanilla OrElse Target.Type = McFolder.Types.RenamedVanilla) AndAlso Target.Location = Paths.Base & ".minecraft\" AndAlso McFolderList.IsSingle, "清空", "删除")
If MyMsgBox("你确定要" & DeleteText & "这个文件夹吗?" & vbCrLf &
@@ -328,7 +339,7 @@ Public Class PageSelectLeft
Dim DeletedName As String = Nothing
Dim Folders = Settings.Get(Of String)("LaunchFolders").Split("|"c, True).ToList
For Each Folder In Folders.ToList
If Not Folder.EndsWithF(Target.Location) Then Continue For
If Not Folder.Contains(">") OrElse Not PathUtils.IsSame(Folder.AfterFirst(">"), Target.Location) Then Continue For
DeletedName = Folder.BeforeFirst(">")
Folders.Remove(Folder)
Exit For
@@ -349,7 +360,7 @@ Public Class PageSelectLeft
End Sub
Public Shared Sub RefreshCurrent(Folder As String)
WriteIni(Folder & "PCL.ini", "InstanceCache", "") '删除缓存以强制要求下一次加载时更新列表
If Folder = McFolderSelected Then LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")
If PathUtils.IsSame(Folder, McFolderSelected) Then LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")
End Sub
Public Sub Rename_Click(sender As Object, e As RoutedEventArgs)
Dim Folder As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag
@@ -365,7 +376,7 @@ Public Class PageSelectLeft
For i = 0 To Folders.Count - 1
Dim FolderCurrent As String = Folders(i)
If FolderCurrent = "" Then Continue For
If FolderCurrent.Split(">")(1) = Folder.Location Then
If PathUtils.IsSame(FolderCurrent.Split(">")(1), Folder.Location) Then
IsAdded = True
If FolderCurrent.Split(">")(0) = NewName Then
'名称未修改
@@ -390,9 +401,9 @@ Public Class PageSelectLeft
'点击选项
Public Sub Folder_Change(sender As MyListItem, e As RouteEventArgs)
If Not e.RaiseByMouse OrElse Not sender.Checked Then Return
'检查是否有下载任务
If HasDownloadingTask(True) Then
Hint("在下载任务进行时,无法切换游戏文件夹!", HintType.Red)
'下载或版本修改期间禁止更改游戏文件夹
If McInstanceLock.IsAnyLocked OrElse HasDownloadingTask(True) Then
Hint("请在其他任务完成后,再切换游戏文件夹!", HintType.Red)
e.Handled = True
Return
End If
@@ -30,6 +30,8 @@ Public Class PageSelectRight
For Each Card As KeyValuePair(Of McInstanceCardType, List(Of McInstance)) In McInstanceList.ToArray
'确认是否为隐藏版本显示状态
If Card.Key = McInstanceCardType.Hidden Xor ShowHidden Then Continue For
Dim Instances = Card.Value.Where(Function(Instance) Not McInstanceLock.IsLocked(Instance)).ToList
If Not Instances.Any Then Continue For
#Region "确认卡片名称"
Dim CardName As String = ""
Select Case Card.Key
@@ -40,11 +42,11 @@ Public Class PageSelectRight
Dim IsNeoForgeExists As Boolean = False
Dim IsFabricExists As Boolean = False
Dim IsLiteExists As Boolean = False
For Each Instance As McInstance In Card.Value
If Instance.Version.HasFabric Then IsFabricExists = True
If Instance.Version.HasLiteLoader Then IsLiteExists = True
If Instance.Version.HasForge Then IsForgeExists = True
If Instance.Version.HasNeoForge Then IsNeoForgeExists = True
For Each Instance As McInstance In Instances
If Instance.Version.ModLoaders.HasFlagF(ModLoaders.Fabric) Then IsFabricExists = True
If Instance.Version.ModLoaders.HasFlagF(ModLoaders.LiteLoader) Then IsLiteExists = True
If Instance.Version.ModLoaders.HasFlagF(ModLoaders.Forge) Then IsForgeExists = True
If Instance.Version.ModLoaders.HasFlagF(ModLoaders.NeoForge) Then IsNeoForgeExists = True
Next
If If(IsLiteExists, 1, 0) + If(IsForgeExists, 1, 0) + If(IsFabricExists, 1, 0) + If(IsNeoForgeExists, 1, 0) > 1 Then
CardName = "可安装 Mod"
@@ -72,17 +74,18 @@ Public Class PageSelectRight
End Select
#End Region
'建立控件
Dim CardTitle As String = CardName & If(CardName = "收藏夹", "", " (" & Card.Value.Count & ")")
Dim NewCard As New MyCard With {.Title = CardTitle, .Margin = New Thickness(0, 0, 0, 15), .SwapType = 0}
Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Card.Value}
Dim CardTitle As String = CardName & If(CardName = "收藏夹", "", " (" & Instances.Count & ")")
Dim NewCard As New MyCard With {.Title = CardTitle, .Margin = New Thickness(0, 0, 0, 15)}
Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0)}
NewCard.Children.Add(NewStack)
NewCard.SwapControl = NewStack
NewCard.SetContentBuilder(Instances, Function(Instance) Instance.ToListItem(AddressOf McInstanceListContent))
PanMain.Children.Add(NewCard)
'确定卡片是否展开
If Card.Key = McInstanceCardType.Rubbish OrElse Card.Key = McInstanceCardType.Error OrElse Card.Key = McInstanceCardType.Fool Then
NewCard.IsSwapped = True
Else
MyCard.StackInstall(NewStack, 0, CardTitle)
NewCard.BuildContent()
End If
Next
@@ -128,7 +131,8 @@ Public Class PageSelectRight
BtnStar.LogoScale = 1.1
BtnStar.Logo = Logo.IconButtonLikeLine
End If
AddHandler BtnStar.Click, Sub()
AddHandler BtnStar.Click,
Sub()
WriteIni(Instance.PathVersion & "PCL\Setup.ini", "IsStar", Not Instance.IsStar)
McInstanceListForceRefresh = True
LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")
@@ -136,7 +140,7 @@ Public Class PageSelectRight
Dim BtnDel As New MyIconButton With {.LogoScale = 1.1, .Logo = Logo.IconButtonDelete}
BtnDel.ToolTip = "删除"
AddHandler BtnDel.Click, Sub() DeleteInstance(sender, Instance)
If Instance.State <> McInstanceState.Error Then
If Instance.State <> McInstance.McInstanceState.Error Then
Dim BtnCont As New MyIconButton With {.LogoScale = 1.1, .Logo = Logo.IconButtonSetup}
BtnCont.ToolTip = "设置"
AddHandler BtnCont.Click,
@@ -166,6 +170,10 @@ Public Class PageSelectRight
'点击选项
Public Shared Sub Item_Click(sender As MyListItem, e As EventArgs)
Dim Instance As McInstance = sender.Tag
If McInstanceLock.IsLocked(Instance) Then
Hint("该版本正在被修改,请稍后再选择!", HintType.Red)
Return
End If
If New McInstance(Instance.PathVersion).Check Then
'正常版本
McInstanceSelected = Instance
@@ -183,10 +191,17 @@ Public Class PageSelectRight
'修改此代码时,同时修改 PageInstanceOverall 中的代码
Public Shared Sub DeleteInstance(Item As MyListItem, Instance As McInstance)
Try
If McInstanceLock.IsLocked(Instance) Then
Hint("该版本正在被修改,请稍后再删除!", HintType.Red)
Return
End If
Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown
Dim IsHintIndie As Boolean = Instance.State <> McInstanceState.Error AndAlso Instance.PathIndie <> McFolderSelected
Dim IsHintIndie As Boolean = Instance.State <> McInstance.McInstanceState.Error AndAlso Instance.PathIndie <> McFolderSelected
Dim SavesFolder = DirectoryUtils.GetInfo(Instance.PathIndie & "saves\")
Dim SaveEntries = If(IsHintIndie AndAlso SavesFolder.Exists, SavesFolder.EnumerateDirectories.OrderByDescending(Function(Save) Save.LastWriteTime).Select(Function(Save) $"{Save.Name}(上次修改:{StringUtils.FormatTimeSpan(Save.LastWriteTime - Date.Now, False)})").ToList, New List(Of String))
Select Case MyMsgBox($"你确定要{If(IsShiftPressed, "永久", "")}删除版本 {Instance.Name} 吗?" &
If(IsHintIndie, vbCrLf & "由于该版本开启了版本隔离,删除版本时该版本对应的存档、资源包、Mod 等文件也将被一并删除!", ""),
If(IsHintIndie, vbCrLf & "该版本对应的存档、资源包、Mod 等文件也将被一并删除!", "") &
If(SaveEntries.Any, vbCrLf & vbCrLf & "这会删除以下存档:" & vbCrLf & "· " & SaveEntries.Join(vbCrLf & "· "), ""),
"版本删除确认", , "取消",, True)
Case 1
Instance.ResetSettingsCache()
@@ -204,7 +219,7 @@ Public Class PageSelectRight
Dim Card As MyCard = Parent.Parent
Card.Title = Card.Title.Replace(Parent.Children.Count - 1, Parent.Children.Count - 2) '有一个占位符
Parent.Children.Remove(Item)
If McInstanceSelected IsNot Nothing AndAlso Instance.PathVersion = McInstanceSelected.PathVersion Then
If McInstanceSelected IsNot Nothing AndAlso PathUtils.IsSame(Instance.PathVersion, McInstanceSelected.PathVersion) Then
'删除当前版本就更改选择
McInstanceSelected = MyVirtualizingElement.TryInit(Parent.Children(0)).Tag
End If
@@ -286,6 +286,7 @@
<local:MyCheckBox Height="28" Text="禁用 Java Launch Wrapper" local:SettingService.Key="LaunchAdvanceDisableJLW" ToolTip="是否使用 Java Launch Wrapper 修复 Java 18- 在中文路径下可能无法正常启动的问题。&#xa;详见:https://github.com/00ll00/java_launch_wrapper" />
<local:MyCheckBox Height="28" Text="禁用 LWJGL Unsafe Agent" local:SettingService.Key="LaunchAdvanceDisableLUA" ToolTip="是否使用 LWJGL Unsafe Agent 修复 LWJGL 3.4.1 的一个性能问题。&#xa;详见:https://github.com/HMCL-dev/lwjgl-unsafe-agent" />
<local:MyCheckBox Height="28" Text="使用高性能显卡" local:SettingService.Key="LaunchAdvanceGraphicCard" ToolTip="自动在 Windows 设置 → 系统 → 屏幕 → 显示卡 中将 PCL 和 Java 改为使用高性能显卡。&#xa;如果同时有集成显卡和独立显卡,这能确保 MC 使用独立显卡进行渲染,以大幅提高帧率。" />
<local:MyCheckBox Height="28" Text="在正版登录时验证 SSL 证书" local:SettingService.Key="ToolDownloadCert" ToolTip="开启验证可以提高安全性,但可能会导致正版登录失败(见 #3018)。&#xa;若登录因为 “未能为 SSL/TLS 安全通道建立信任关系” 失败,请考虑关闭此选项。" />
</StackPanel>
<local:MyHint Text="版本独立设置中还有更多高级选项可供调整。" Margin="0,5,0,10" Theme="Blue" Grid.Row="11" Grid.ColumnSpan="2"
CanClose="True" RelativeSetup="HintMoreAdvancedSetup" />
@@ -396,7 +396,7 @@ PreFin:
AddHandler DownButton.Click, Sub() MoveJavaInList(JavaEntry, 1)
Buttons.Add(DownButton)
'从列表中移除按钮
Dim IsOfficial As Boolean = JavaEntry.Folder.StartsWithF($"{Paths.AppData}.minecraft\runtime\")
Dim IsOfficial As Boolean = PathUtils.IsParentOf($"{Paths.AppData}.minecraft\runtime\", JavaEntry.Folder)
Dim DeleteButton As New MyIconButton With {.Logo = Logo.IconButtonStop, .Height = 24, .Width = 24}
DeleteButton.IsEnabled = Not IsOfficial
DeleteButton.ToolTip = If(DeleteButton.IsEnabled, "从列表中移除", "无法移除官方 Java")
@@ -22,7 +22,6 @@
<RowDefinition Height="27" />
<RowDefinition Height="27" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Text="文件下载源" Margin="0,0,25,0" />
<local:MyComboBox x:Name="ComboDownloadSource" Grid.ColumnSpan="2" local:SettingService.Key="ToolDownloadSource" Grid.Column="1">
@@ -44,9 +43,6 @@
ToolTip="设置下载的速度上限,以避免在下载时导致其他需要联网的程序卡死" />
<TextBlock VerticalAlignment="Top" Grid.Row="6" HorizontalAlignment="Left" Text="目标文件夹" Margin="0,5,25,5" />
<TextBlock Margin="0,5" HorizontalAlignment="Left" Text="请在 启动 → 版本选择 → 文件夹列表 中更改下载目标文件夹。&#xa;在某个文件夹或游戏版本上右键,即可选择打开对应文件夹。" Grid.Row="6" Grid.Column="1" VerticalAlignment="Center" Opacity="0.5" />
<local:MyCheckBox Margin="0,2,0,4" Text="在正版登录时验证 SSL 证书" Grid.Row="7" Height="22" Grid.ColumnSpan="2"
x:Name="CheckDownloadCert" local:SettingService.Key="ToolDownloadCert"
ToolTip="开启验证可以提高安全性,但可能会导致正版登录失败(见 #3018)。&#xa;若登录因为 “未能为 SSL/TLS 安全通道建立信任关系” 失败,请考虑关闭此选项。" />
</Grid>
</StackPanel>
</local:MyCard>
@@ -64,7 +60,6 @@
<RowDefinition Height="7" />
<RowDefinition Height="28" />
<RowDefinition Height="7" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="来源" Margin="0,0,25,0" />
<local:MyComboBox x:Name="ComboDownloadMod" Grid.Row="0" Grid.ColumnSpan="2" local:SettingService.Key="ToolDownloadMod" Grid.Column="1">
@@ -87,8 +82,6 @@
<local:MyComboBoxItem Content="标题显示译名,详情显示文件名" IsSelected="True" />
<local:MyComboBoxItem Content="标题显示文件名,详情显示译名" />
</local:MyComboBox>
<local:MyCheckBox Margin="0,2,0,4" Text="在显示 Mod 加载器时忽略 Quilt" Grid.Row="6" Height="22" Grid.ColumnSpan="2"
x:Name="CheckDownloadIgnoreQuilt" local:SettingService.Key="ToolDownloadIgnoreQuilt" />
</Grid>
</StackPanel>
</local:MyCard>
@@ -235,16 +235,16 @@
<local:MyHint Margin="0,15,0,-4" Text="内容见 ModSetup" Theme="Blue" x:Name="HintCustom" />
<local:MyHint Margin="0,15,0,6" Text="请谨慎使用陌生人提供的主页,恶意代码可能会造成损害!" Theme="Yellow" x:Name="HintCustomWarn"
CanClose="True" RelativeSetup="HintCustomWarn" />
<Grid Height="35" x:Name="PanCustomLocal" Visibility="Collapsed" Margin="0,10,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
<ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
</Grid.ColumnDefinitions>
<local:MyButton x:Name="BtnCustomRefresh" Grid.Column="0" MinWidth="140" Text="刷新主页" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
<local:MyButton x:Name="BtnCustomFile" Grid.Column="1" MinWidth="140" Text="生成教学文件" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
<local:MyButton x:Name="BtnCustomTutorial" Grid.Column="2" MinWidth="140" Text="查看教程" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
</Grid>
<WrapPanel x:Name="PanCustomLocal" Visibility="Collapsed" Margin="0,0,0,5">
<local:MyButton x:Name="BtnCustomRefresh" Text="刷新主页"
MinWidth="140" Height="35" Padding="13,0" Margin="0,10,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
<local:MyButton x:Name="BtnCustomFile" Text="生成教学文件"
MinWidth="140" Height="35" Padding="13,0" Margin="0,10,20,0" HorizontalAlignment="Left" />
<local:MyButton x:Name="BtnCustomTutorial" Text="查看教程"
MinWidth="140" Height="35" Padding="13,0" Margin="0,10,20,0" HorizontalAlignment="Left" />
<local:MyButton x:Name="BtnCustomFolder" Text="打开主页文件夹"
MinWidth="140" Height="35" Padding="13,0" Margin="0,10,20,0" HorizontalAlignment="Left" />
</WrapPanel>
<Grid x:Name="PanCustomNet" Visibility="Collapsed" Margin="0,10,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Name" />
@@ -284,6 +284,13 @@
<local:MyComboBoxItem local:SettingService.Value="15" Content="Minecraft 芝士站(作者:awaEric233)" />
<local:MyComboBoxItem local:SettingService.Value="16" Content="整合包推荐引擎(作者:CHMYGZ)" />
<local:MyComboBoxItem local:SettingService.Value="17" Content="Bangumi 番剧主页(作者:wzyaeu)" />
<local:MyComboBoxItem local:SettingService.Value="18" Content="Bilibili 热门(作者:wzyaeu)" />
<local:MyComboBoxItem local:SettingService.Value="19" Content="Music 云音乐热门(作者:wzyaeu)" />
<local:MyComboBoxItem local:SettingService.Value="20" Content="模组推荐(作者:llsgllsg)" />
<local:MyComboBoxItem local:SettingService.Value="21" Content="每日游戏推荐(作者:llsgllsg)" />
<local:MyComboBoxItem local:SettingService.Value="22" Content="Wallpaper 每日壁纸(作者:wzyaeu)" />
<local:MyComboBoxItem local:SettingService.Value="23" Content="Weather 天气预报(作者:wzyaeu)" />
<local:MyComboBoxItem local:SettingService.Value="24" Content="新一代狗屁王大杂烩主页(作者:llsgllsg)" />
</local:MyComboBox>
</Grid>
</StackPanel>
@@ -330,6 +337,7 @@
<local:MyCheckBox Height="22" VerticalAlignment="Center" Grid.Row="4" Grid.Column="3" Text="版本管理" x:Name="CheckHiddenFunctionSelect" local:SettingService.Key="UiHiddenFunctionSelect" ToolTip="隐藏启动按钮下方的版本选择与版本设置入口。" />
<local:MyCheckBox Height="22" VerticalAlignment="Center" Grid.Row="4" Grid.Column="4" Text="Mod 更新" x:Name="CheckHiddenFunctionModUpdate" local:SettingService.Key="UiHiddenFunctionModUpdate" ToolTip="禁止在 Mod 管理页面更新 Mod。" />
<local:MyCheckBox Height="22" VerticalAlignment="Center" Grid.Row="4" Grid.Column="5" Text="功能隐藏" x:Name="CheckHiddenFunctionHidden" local:SettingService.Key="UiHiddenFunctionHidden" ToolTip="仅隐藏本选项卡,设置的禁用内容依然有效。&#xa;可以在保留个性化设置页面的情况下避免隐藏设置被修改。" />
<local:MyCheckBox Height="22" VerticalAlignment="Center" Grid.Row="4" Grid.Column="6" Text="版本修改" x:Name="CheckHiddenFunctionModify" local:SettingService.Key="UiHiddenFunctionInstanceModify" ToolTip="隐藏版本设置中的版本修改功能。" />
</Grid>
</StackPanel>
</local:MyCard>
@@ -341,6 +341,14 @@ Refresh:
End Select
FrmSetupUI.CardCustom.TriggerForceResize()
End Sub
Private Sub BtnCustomFolder_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnCustomFolder.Click
Dim CustomPath As String = Paths.Base & "PCL\Custom.xaml"
If FileUtils.Exists(CustomPath) Then
OpenExplorer(CustomPath)
Else
OpenExplorer(Paths.Base & "PCL\")
End If
End Sub
'主题
Private Sub LabLauncherTheme5Unlock_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles LabLauncherTheme5Unlock.MouseLeftButtonUp
@@ -445,6 +453,10 @@ Refresh:
If FrmSetupUI IsNot Nothing Then
FrmSetupUI.CardSwitch.Visibility = If(Not HiddenForceShow AndAlso Settings.Get(Of Boolean)("UiHiddenFunctionHidden"), Visibility.Collapsed, Visibility.Visible)
End If
'版本设置子页面
If FrmInstanceLeft IsNot Nothing Then
FrmInstanceLeft.ItemModify.Visibility = If(Not HiddenForceShow AndAlso Settings.Get(Of Boolean)("UiHiddenFunctionInstanceModify"), Visibility.Collapsed, Visibility.Visible)
End If
'设置子页面
If FrmSetupLeft IsNot Nothing Then
FrmSetupLeft.ItemLaunch.Visibility = If(Not HiddenForceShow AndAlso Settings.Get(Of Boolean)("UiHiddenSetupLaunch"), Visibility.Collapsed, Visibility.Visible)
@@ -120,7 +120,6 @@ Public Class Settings
New Setting("ToolDownloadVersion", 1, Source:=Sources.Registry),
New Setting("ToolDownloadTranslate", 0, Source:=Sources.Registry),
New Setting("ToolDownloadTranslateV2", 1, Source:=Sources.Registry),
New Setting("ToolDownloadIgnoreQuilt", True, Source:=Sources.Registry),
New Setting("ToolDownloadCert", False, Source:=Sources.Registry, OnChanged:=AddressOf ModNet.ShouldValidateSslCertificateOnLogin),
New Setting("ToolDownloadMod", 2, Source:=Sources.Registry),
New Setting("ToolModLocalNameStyle", 0, Source:=Sources.Registry),
@@ -159,6 +158,7 @@ Public Class Settings
New Setting("UiHiddenPageOther", False, OnChanged:=AddressOf PageSetupUI.HiddenRefresh),
New Setting("UiHiddenFunctionSelect", False, OnChanged:=AddressOf PageSetupUI.HiddenRefresh),
New Setting("UiHiddenFunctionModUpdate", False, OnChanged:=AddressOf PageSetupUI.HiddenRefresh),
New Setting("UiHiddenFunctionInstanceModify", False, OnChanged:=AddressOf PageSetupUI.HiddenRefresh),
New Setting("UiHiddenFunctionHidden", False, OnChanged:=AddressOf PageSetupUI.HiddenRefresh),
New Setting("UiHiddenSetupLaunch", False, OnChanged:=AddressOf PageSetupUI.HiddenRefresh),
New Setting("UiHiddenSetupUi", False, OnChanged:=AddressOf PageSetupUI.HiddenRefresh),
@@ -261,7 +261,11 @@ Public Class Settings
Case Sources.Normal
WriteIni("Setup", Key, Value)
Case Sources.Registry
WriteReg(Key, Value)
Try
RegistryUtils.Write($"HKEY_CURRENT_USER\Software\{RegFolder}", Key, Value)
Catch ex As Exception
Logger.Warn(ex, $"写入注册表失败({Key} → {Value})", LogBehavior.Toast)
End Try
Case Sources.Instance
If Instance Is Nothing Then Throw New Exception($"保存版本独立设置 {Key} 时未提供目标版本")
WriteIni(Instance.PathVersion & "PCL\Setup.ini", Key, Value)
@@ -327,7 +331,7 @@ Public Class Settings
Case Sources.Normal
GotValue = ReadIni("Setup", Key, DefaultValue)
Case Sources.Registry
GotValue = ReadReg(Key, DefaultValue)
GotValue = RegistryUtils.TryRead($"HKEY_CURRENT_USER\Software\{RegFolder}", Key, DefaultValue)
Case Sources.Instance
If Instance Is Nothing Then
Throw New Exception($"读取版本设置 {Key} 时未提供目标版本")
@@ -386,7 +390,11 @@ Public Class Settings
Case Sources.Normal
DeleteIniKey("Setup", Key)
Case Sources.Registry
DeleteReg(Key)
Try
RegistryUtils.Delete($"HKEY_CURRENT_USER\Software\{RegFolder}", Key)
Catch ex As Exception
Logger.Warn(ex, $"删除注册表失败({Key})", LogBehavior.Toast)
End Try
Case Sources.Instance
If Instance Is Nothing Then Throw New Exception($"重置版本设置 {Key} 时未提供目标版本")
DeleteIniKey(Instance.PathVersion & "PCL\Setup.ini", Key)
@@ -414,7 +422,12 @@ Public Class Settings
Case Sources.Normal
Return HasIniKey("Setup", Key)
Case Sources.Registry
Return HasReg(Key)
Try
Return RegistryUtils.Has($"HKEY_CURRENT_USER\Software\{RegFolder}", Key)
Catch ex As Exception
Logger.Warn(ex, $"判断注册表 {Key} 是否存在时出错")
Return False
End Try
Case Else 'Source.Instance
If Instance Is Nothing Then Throw New Exception($"判断版本设置 {Key} 是否存在时未提供目标版本")
Return HasIniKey(Instance.PathVersion & "PCL\Setup.ini", Key)
@@ -32,7 +32,7 @@ Public Class PageSpeedLeft
'定时器任务
Private ReadOnly RightCards As New Dictionary(Of String, MyCard)
Private Sub Watcher()
If Not FrmMain.PageCurrent = FormMain.PageType.DownloadManager Then Return
If Not FrmMain.PageCurrent = FormMain.PageType.TaskManager Then Return
Try
#Region "更新左边栏"
@@ -55,7 +55,7 @@ Public Class PageSpeedLeft
#End Region
Catch ex As Exception
Logger.Error(ex, "下载管理左栏监视出错")
Logger.Error(ex, "任务管理左栏监视出错")
End Try
If FrmSpeedRight Is Nothing OrElse FrmSpeedRight.PanMain Is Nothing Then Return
Try
@@ -63,7 +63,7 @@ Public Class PageSpeedLeft
TaskRefresh(Loader)
Next
Catch ex As Exception
Logger.Error(ex, "下载管理右栏监视出错")
Logger.Error(ex, "任务管理右栏监视出错")
End Try
End Sub
Public Sub TaskRefresh(Loader As LoaderBase)
@@ -106,7 +106,7 @@ Public Class PageSpeedLeft
#Region "进度不同,更新卡片"
Try
If Card.Children.Count < LoaderList.Count * 2 Then
Logger.Warn($"刷新下载管理卡片 {Loader.Name} 失败:卡片中仅有 {Card.Children.Count} 个子项,要求至少有 {LoaderList.Count * 2} 个子项")
Logger.Warn($"刷新任务管理卡片 {Loader.Name} 失败:卡片中仅有 {Card.Children.Count} 个子项,要求至少有 {LoaderList.Count * 2} 个子项")
Exit Try
End If
Dim Row As Integer = 0
@@ -133,12 +133,12 @@ Public Class PageSpeedLeft
Row += 1
Next
Catch ex As Exception
Logger.Error(ex, $"刷新下载管理卡片 {Loader.Name} 失败")
Logger.Error(ex, $"刷新任务管理卡片 {Loader.Name} 失败")
End Try
#End Region
End Select
Catch ex As Exception
Logger.Error(ex, $"更新下载管理显示失败({Loader.State})")
Logger.Error(ex, $"更新任务管理显示失败({Loader.State})")
End Try
ElseIf Not (Loader.State = LoadState.Canceled OrElse Loader.State = LoadState.Finished) Then
Try
@@ -177,23 +177,23 @@ Public Class PageSpeedLeft
Try
Card = GetObjectFromXML(CardXAML)
Catch ex As Exception
Logger.Warn(ex, "新建下载管理卡片失败")
Logger.Warn(ex, "新建任务管理卡片失败")
Logger.Info($"出错的卡片内容:{vbCrLf}{CardXAML}")
Throw
End Try
FrmSpeedRight.PanMain.Children.Insert(0, Card)
RightCards.Add(Loader.Name, Card)
Logger.Info($"新建下载管理卡片:{Loader.Name}")
Logger.Info($"新建任务管理卡片:{Loader.Name}")
'添加取消按钮
Dim Cancel As New MyIconButton With {.Name = "BtnCancel", .Logo = "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", .Height = 20, .Margin = New Thickness(0, 10, 10, 0), .LogoScale = 1.1, .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Top}
Card.Children.Add(Cancel)
AddHandler Cancel.Click,
Sub(sender As MyIconButton, e As EventArgs)
AniDispose(sender, False)
AniDispose(Card, True, Sub() If FrmSpeedRight.PanMain.Children.Count = 0 AndAlso FrmMain.PageCurrent = FormMain.PageType.DownloadManager Then FrmMain.PageBack())
AniDispose(Card, True, Sub() If FrmSpeedRight.PanMain.Children.Count = 0 AndAlso FrmMain.PageCurrent = FormMain.PageType.TaskManager Then FrmMain.PageBack())
RightCards.Remove(Loader.Name)
LoaderTaskbar.Remove(Loader)
Logger.Info($"关闭下载管理卡片:{Loader.Name},且移出任务列表")
Logger.Info($"关闭任务管理卡片:{Loader.Name},且移出任务列表")
RunInThread(Sub() Loader.Cancel())
End Sub
'如果已经失败,再刷新一次,修改成失败的控件
@@ -203,11 +203,11 @@ Public Class PageSpeedLeft
End If
#End Region
Catch ex As Exception
Logger.Error(ex, "添加下载管理卡片失败")
Logger.Error(ex, "添加任务管理卡片失败")
End Try
End If
Catch ex As Exception
Logger.Error(ex, "刷新下载管理显示失败")
Logger.Error(ex, "刷新任务管理显示失败")
End Try
End Sub
Public Sub TaskRemove(Loader As Object)
@@ -218,7 +218,7 @@ Public Class PageSpeedLeft
Dim Card As Grid = RightCards(Loader.Name)
FrmSpeedRight.PanMain.Children.Remove(Card)
RightCards.Remove(Loader.Name)
Logger.Info($"移除下载管理卡片:{Loader.Name}")
Logger.Info($"移除任务管理卡片:{Loader.Name}")
End Sub)
End If
End Sub
@@ -227,7 +227,7 @@ Public Class PageSpeedLeft
''' 若没有任务,尝试返回主页。
''' </summary>
Private Sub TryReturnToHome()
If FrmSpeedRight.PanMain.Children.Count = 0 AndAlso FrmMain.PageCurrent = FormMain.PageType.DownloadManager Then
If FrmSpeedRight.PanMain.Children.Count = 0 AndAlso FrmMain.PageCurrent = FormMain.PageType.TaskManager Then
FrmMain.PageBack()
End If
End Sub
@@ -282,6 +282,7 @@
<Compile Include="Pages\PageDownload\PageDownloadInstall.xaml.vb">
<DependentUpon>PageDownloadInstall.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\PageDownload\PageDownloadModify.vb" />
<Compile Include="Pages\PageDownload\Resource\PageDownloadCompDetail.xaml.vb">
<DependentUpon>PageDownloadCompDetail.xaml</DependentUpon>
</Compile>
@@ -939,9 +940,14 @@
<LogicalName>MeloongCore.Wpf.dll</LogicalName>
<Link>Resources\MeloongCore.Wpf.dll</Link>
</None>
<None Include="..\PCLCS\bin\Microsoft.Win32.Registry.dll">
<LogicalName>Microsoft.Win32.Registry.dll</LogicalName>
<Link>Resources\Microsoft.Win32.Registry.dll</Link>
</None>
</ItemGroup>
<ItemGroup>
<Resource Include="Images\Heads\ChongQing.png" />
<Resource Include="Images\Heads\HerobrineXia.png" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
Binary file not shown.
+1 -1
View File
@@ -12,6 +12,6 @@
<br/>
### 相关资源
- [PCL 下载](https://meloong.com/afd/p/0164034c016c11ebafcb52540025c377):下载可免费使用的正式版 PCL!
- [PCL 下载](https://meloong.com/pcl):下载可免费使用的正式版 PCL!
- [PCL 功能投票](https://github.com/Meloong-Git/PCL/discussions/2):来参加投票吧,开发者会优先处理票数较高的项目!
- [帮助文档库](https://github.com/LTCatt/PCL2Help):PCL 内置帮助文档的存储库
Loaded 100 of 105 files, more files were not shown because too many files have changed in this diff. Show more